How to use default property descriptors and successfully assign from __init__()?
请问这个的正确成语是什么?
我想定义一个对象,该对象包含可以(可选)从dict初始化的属性(dict来自json;它可能不完整)。稍后,我可以通过setter修改属性。实际上有13个以上的属性,我希望能够使用默认的getter和setter,但这似乎不适用于这种情况:
但我不想为所有的prop1... propn编写明确的描述符。另外,我想把默认分配从__init__()移到访问器中…但是我需要一个表达式描述符。
最优雅的解决方案是什么?(除了将所有setter调用从__init__()移到方法/类方法_make()之外?)
[删除的注释使用默认描述符的badprop代码是由以前的so用户的注释引起的,他给人的印象是它给了您一个默认的setter。但事实并非如此——setter是未定义的,它必然会抛出AttributeError]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class DubiousPropertyExample(object):
def __init__(self,dct=None):
self.prop1 = 'some default'
self.prop2 = 'other default'
#self.badprop = 'This throws AttributeError: can\'t set attribute'
if dct is None: dct = dict() # or use defaultdict
for prop,val in dct.items():
self.__setattr__(prop,val)
# How do I do default property descriptors? this is wrong
#@property
#def badprop(self): pass
# Explicit descriptors for all properties - yukk
@property
def prop1(self): return self._prop1
@prop1.setter
def prop1(self,value): self._prop1 = value
@property
def prop2(self): return self._prop2
@prop2.setter
def prop2(self,value): self._prop2 = value
dub = DubiousPropertyExample({'prop2':'crashandburn'})
print dub.__dict__
# {'_prop2': 'crashandburn', '_prop1': 'some default'} |
如果在第5行self.badprop = ...未注释的情况下运行此命令,则失败:
1
| self.badprop = 'This throws AttributeError: can\'t set attribute' |
attributeError:无法设置属性
[和以往一样,我阅读了关于描述符、隐式描述符、从init调用它们的so文章]
- …我搞糊涂了。这里有什么问题?
- 如果在未注释第5行self.badprop = ...的情况下运行此命令,则会失败,因为它认为badprop是属性而不是属性。显然,访问器还不可见。
- 不,它失败是因为你没有设置一个setter。
- 它认为badprop是一个属性(不是描述符,btw),但它是一个没有setter的属性。
- 看起来你根本不需要使用属性。只需使用常规成员变量,将其初始化为默认值,然后用字典中的任何值覆盖。
- @SR2222也是一个描述符。
- 那好吧。问题归结为"如何在没有所有属性的显式描述符的情况下编写此代码?"
- @那么你的问题太含糊了,无法回答。您需要发布一个更具代表性的示例,因为如文中所述,答案是"不要使用属性"。
- 请相信我,我需要属性,以备将来涉及13+属性的代码使用。现在告诉我如何为它们获取隐式描述符。
- @朱利安,从技术上说是的,但我想这让人困惑。
- 所以这又回到了我最初的问题。动态创建属性很容易,但是需要定义getter和setter要做什么。
- @我认为问题出在你的术语"隐式描述符"上。这对我来说没有任何意义,我认为其他的评论者也同样感到困惑。(Google搜索"python"隐式描述符""只返回此页。)
- @BLCKKnight:"如何使用默认描述符",然后。至少一个so用户以前使用过"隐式描述符"。
- 不要用dct={}作为默认参数…使用dct = None,然后检查None,在方法中创建一个新的dict。
- @米吉尔森:很好的文体观点(谢谢),但我的问题与主题无关。
- @SR2222:这非常令人讨厌,而且这并不是问题的答案:"我如何在没有所有属性的显式描述符的情况下编写代码?"如果答案是"你不能,你需要13*2个描述符",那么简单地说,没有粗鲁。如果你有比写13*2描述符更好的代码习惯用法,请给我看看……这种代码不可扩展。所以我的问题。
- @问题是没有默认描述符这样的东西。您可以单独使用@property,因为您没有给定setter,所以它"默认"为未设置。如果您告诉我们您希望这些属性的效果是什么,我们可能会告诉您如何实现它。但是术语"默认描述符"对我们来说并不意味着什么,除非它意味着一个只读属性,您已经说过这不是您想要的。
- 我不能给你复制粘贴代码,除非你告诉我你的规范是什么,即使我想。您需要这些属性如何工作?getter/setter是否遵循某种可以封装的一般规则集?或者你只是在尝试将private/get/set设置为python?
- 以前有一个这样的用户发布了消息,如果您只写@property def foo(): pass,那么您将得到默认描述符。所以他是错误信息的来源。
- 在定义属性时,如果不指定setter、deleter或doc,则在技术上会得到默认行为。默认行为是抛出异常,因为功能尚未实现。
- @SR2222,我只想得到一个完全普通的普通描述符(读写属性)。没有为每个都写一个显式的设置器。如果不可能的话,告诉我。
- @smci python将普通的普通描述符(读写属性)视为具有正常属性行为的普通属性,根本不需要编写任何代码。描述符是获得异常行为的一种方法。
- 这是可能的。在几乎所有情况下,这也是毫无意义的。90%的时候,直接成员访问是一种方式。9.99%的情况下,您希望按照以下所述使用__getattr__和__setattr__。其余的都是非常特殊的情况,例如,如果出于某种原因,您希望在基类中定义默认的getter和setter,以便可以重写子类中的行为,但是出于某种原因,您不能直接在子类中创建属性。我无法想象会发生什么,但我不排除。所以问题是,为什么您需要这个功能?
- 嗯。请说明您将如何决定何时使用直接成员访问以及何时重定向__getattr__/__setattr__?
- 你在逆谷粒前进。Python不是Java。当需要时,很容易将普通属性更改为属性。当您有一个实际需要属性的示例时,请回来询问如何将常规属性更改为属性。我们很乐意帮忙。
- 我发现的一个主要的用例是,出于某种原因,我希望将数据存储在成员属性中,而不是直接存储到__dict__中,但希望它的成员可以作为属性从对象的公共接口访问。就像你在这里所做的。
- @SR2222和@gnibler:是的,因为写x.a,x.b,x.c,x.d比x['a'],x['b'],x['c'],x['d']更清晰和优雅。还有其他的原因,但是我还没有写代码,所以我现在不能说。请随意告诉我你的。
- 这正是您使用__getattr__和__setattr__所获得的访问类型。
- 啊哈!谢谢SR2222!所以你不觉得性病医生是在做解释吗?几乎所有关于房产的讨论?
我觉得你有点误解了物业是如何运作的。没有"默认设置器"。它在设置badprop时抛出了一个AttributeError,不是因为它还不知道badprop是一个属性而不是一个普通属性(如果是这样的话,它只会毫无错误地设置该属性,因为这现在是普通属性的行为),而是因为您没有为badprop提供一个setter,只有一个getter。
看看这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| >>> class Foo(object):
@property
def foo(self):
return self._foo
def __init__(self):
self._foo = 1
>>> f = Foo()
>>> f.foo = 2
Traceback (most recent call last):
File"<pyshell#12>", line 1, in <module>
f.foo = 2
AttributeError: can't set attribute |
在构造实例之后,甚至不能从__init__外部设置这样的属性。如果您只使用@property,那么您拥有的是只读属性(实际上是一个看起来像属性读取的方法调用)。
如果您在getter和setter中所做的一切都是将读/写访问重定向到一个同名的属性,但前面加了一个下划线,那么到目前为止,最简单的事情就是完全去掉这些属性,只使用普通属性。Python不是Java(甚至在Java中,我也不相信私有领域和显而易见的公共吸气剂/定位器的优点)。外部世界可以直接访问的属性是"公共"接口的一个非常合理的部分。如果稍后发现在读取/写入属性时需要运行一些代码,则可以在不更改接口的情况下使其成为属性(这实际上是描述符最初的目的,而不是我们可以开始为每一个属性编写Java风格的GETSt/Stter)。
如果您实际上在属性中执行了一些操作,而不是更改属性的名称,并且您希望属性是只读的,那么最好的办法可能是将__init__中的初始化视为直接使用预加下划线设置基础数据属性。然后您的类可以直接初始化而不使用AttributeErrors,然后属性将在读取属性时执行它们的操作。
如果您实际上在属性中执行了一些操作,而不是更改属性的名称,并且您希望属性是可读和可写的,那么您需要实际指定在获取/设置属性时会发生什么。如果每个属性都有独立的自定义行为,那么没有比为每个属性显式提供getter和setter更有效的方法了。
如果在每个getter/setter中运行完全相同(或非常相似)的代码(而不仅仅是在真正的属性名中添加下划线),那么这就是为什么您拒绝将它们全部写出(正确地说!)那么,通过实施一些__getattr__、__getattribute__和__setattr__,您可能会得到更好的服务。这些允许您每次将属性读/写重定向到相同的代码(以属性的名称作为参数),而不是每个属性的两个函数(获取/设置)。
- 你对重定向__getattr__, __getattribute__, and __setattr__而不是使用属性的评论是我一直在努力解决的问题。反对使用属性而不是属性的论点是什么?如果我们在同一个对象中混合使用属性和属性,会不会很难看?
- @它一点也不丑。事实上这是正常的。事实上,这是必要的。毕竟,您的属性试图将prop1重定向到_prop1,_prop1是一个正常属性。如果一个给定的属性需要具有可写的行为,并且让一个read在没有其他自定义的情况下检索最后一个写入的值,那么实现这一点的最简单和最直接的方法就是让它成为一个属性。如果同一对象上的另一个属性需要具有自定义行为,则将该属性设置为属性。
- 属性是作为成员属性附加到类的特殊对象,该类在封面下实现描述符协议以覆盖默认的成员访问机制。属性通常用于特定成员需要特殊访问规则的情况。__getattr__/__setattr__是指当你需要一种系统的、实例范围广泛的特殊行为时。
- @smci描述符的整个要点是模糊这两种情况之间的区别,因此您可以在这两种情况之间来回切换实现,而不会破坏接口。
- 好吧,我从很多对文档的阅读中得出了错误的印象:a)属性比属性"更好",在某种不确定的方式下(比如说,迭代obj.uu dict_uuuuuuuuuu时的稳定性)和b)属性和属性不应该自由混合-显然我的印象是错误的。必须说本的评论比性病医生更清楚。谢谢。
- 属性只是类属性。当您试图访问实例上的属性时,解释器将遍历MRO(实例、实例的类、实例的类的父类等),直到找到一个不引发该名称的attributeError的对象。如果返回值实现了描述符协议,则视情况调用__set__或__get__,否则直接对提供的引用进行操作。属性和__getattr____setattr__上的文档没有真正覆盖彼此,因为除了高级用例之外,它们都超出了对方的范围。
这看起来最简单的方法就是实现__getattr__和__setattr__,这样它们就可以访问解析后的json dict中的任何键,您应该将其设置为实例成员。或者,您可以使用解析的JSON在self.__dict__上调用update(),但这并不是最好的方法,因为这意味着输入dict可能会践踏实例的成员。
对于setter和getter,只有当它们实际执行了一些特殊的操作,而不是直接设置或检索相关的值时,才应该创建它们。Python不是Java(或C++或其他任何东西),不应该试图模仿那些语言中常见的私有/SET/GET范式。