【问题标题】:How to use default property descriptors and successfully assign from __init__()?如何使用默认属性描述符并从 __init__() 成功分配?
【发布时间】:2012-08-17 10:20:15
【问题描述】:

请问正确的成语是什么?

我想定义一个包含属性的对象,该对象可以(可选地)从字典初始化(字典来自 JSON;它可能不完整)。稍后我可能会通过设置器修改属性。 实际上有 13 多个属性,我希望能够使用默认的 getter 和 setter,但这似乎不适用于这种情况:

但我不想为所有 prop1... propn 编写显式描述符 另外,我想将默认分配从__init__() 移出并移到访问器中……但是我需要显式描述符。

最优雅的解决方案是什么? (除了将所有 setter 调用移出__init__() 并移入方法/类方法_make()?)

[DELETED COMMENT 使用默认描述符的 badprop 的代码是由于以前的 SO 用户的评论,他给人的印象是它给了你一个默认的 setter。但它没有 - setter 是未定义的,它必然会抛出 AttributeError。]

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 = ... 未注释的情况下运行它,它会失败:

self.badprop = 'This throws AttributeError: can\'t set attribute'

AttributeError: 无法设置属性

[和以往一样,我阅读了关于描述符、隐式描述符、从 init 调用它们的 SO 帖子]

【问题讨论】:

  • 不,它失败了,因为你没有设置一个 setter。
  • 不要使用dct={}作为默认参数...使用dct = None然后检查None并在方法内创建一个新的dict。
  • @sr2222:这太令人讨厌了,这不是问题的答案“如果没有所有属性的显式描述符,我如何编写这段代码?”如果答案是 “你不能,你需要13*2个描述符”,然后就这么说吧,不要粗鲁。如果您有比编写 13*2 描述符更好的代码习惯,请告诉我……那种代码不可扩展。因此我的问题。
  • @smci Python 将普通的 vanilla 描述符(可读写属性)视为具有属性正常行为的普通属性,根本不需要编写任何代码。描述符是获取异常行为的一种方式。
  • 有可能。在几乎所有情况下,这也是毫无意义的。 90% 的情况下,直接会员访问是可行的方法。 9.99% 的时间,您想使用__getattr____setattr__,如下所述。其余的是超级特殊情况,例如如果出于某种原因您想在基类中定义默认的 getter 和 setter,以便您可以覆盖子类中的行为,但由于某种原因,您不能直接在子类。我无法想象这将如何发生,但我不排除它。那么问题来了,为什么需要这个功能?

标签: python properties default propertydescriptor


【解决方案1】:

似乎最简单的方法就是实现__getattr____setattr__,这样它们就可以访问您解析的JSON 字典中的任何键,您应该将其设置为实例成员。或者,您可以使用解析的 JSON 在 self.__dict__ 上调用 update(),但这并不是最好的处理方式,因为这意味着您的输入 dict 可能会践踏您的实例成员。

至于你的 setter 和 getter,你应该只在它们实际上做了一些特殊的事情而不是直接设置或检索有问题的值时才创建它们。 Python 不是 Java(或 C++ 或其他任何东西),您不应该尝试模仿这些语言中常见的私有/设置/获取范式。

【讨论】:

    【解决方案2】:

    我认为您对属性的工作方式有些误解。没有“默认设置器”。它在设置badprop 时抛出AttributeError 不是因为它还不知道badprop 是一个属性而不是一个普通属性(如果是这种情况,它只会设置没有错误的属性,因为现在是正常属性的行为),但是因为您没有为 badprop 提供 setter,所以只提供了一个 getter。

    看看这个:

    >>> 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 中所做的只是将读/写访问重定向到同名但带有下划线的属性,那么 far 最简单的做法就是获取完全摆脱属性,只使用普通属性。 Python 不是 Java(即使在 Java 中,我也不相信私有字段具有明显的公共 getter/setter 的优点)。外部世界可以直接访问的属性是您的“公共”界面的一个完全合理的部分。如果您后来发现在读取/写入属性时需要运行一些代码,您可以在不更改接口的情况下将其设为属性 then(这实际上是描述符最初的用途,不是,这样我们就可以开始为每个属性编写 Java 风格的 getter/setter)。

    如果您实际上是在属性中做一些事情而不是更改属性的名称,并且您确实希望您的属性是只读的,那么您最好的选择可能是在__init__ 直接设置带有下划线的底层数据属性。然后您的类可以在没有AttributeErrors 的情况下直接初始化,然后属性将在读取属性时执行其操作。

    如果您实际上是在属性中做一些事情而不是更改属性的名称,并且您希望属性可读可写,那么您需要实际指定获取/设置它们时会发生什么。如果每个属性都有独立的自定义行为,那么没有比为每个属性显式提供 getter 和 setter 更有效的方法了。

    如果您在每个 getter/setter 中运行完全相同(或非常相似)的代码(而且不仅仅是在真实属性名称中添加下划线),这就是您反对将它们全部写出来的原因(正确所以!),那么通过实现__getattr____getattribute____setattr__ 中的一些可能会更好。这些允许您每次将属性读取/写入重定向到 same 代码(使用属性名称作为参数),而不是每个属性的两个函数(获取/设置)。

    【讨论】:

    • 您对重定向__getattr__, __getattribute__, and __setattr__ 而不是使用属性的评论是我一直在努力解决的问题。反对使用属性而不是属性的论点是什么?如果我们在同一个对象中混合使用属性和属性会很丑吗?
    • @smci 一点也不丑。事实上,这很正常。事实上,这是必要的。毕竟,您的属性试图将prop1 重定向到_prop1,而_prop1 是一个普通属性。如果给定的属性需要具有可写的行为,并且读取检索最后写入的值,没有其他自定义,那么实现它的最简单和最直接的方法是让它成为一个属性。如果同一对象上的另一个属性需要具有自定义行为,则将其设为属性。
    • 属性是作为成员属性附加到类的特殊对象,该类在后台实现描述符协议以覆盖默认成员访问机制。属性通常用于特定成员需要特殊访问规则的情况。 __getattr__/__setattr__ 是您需要以系统的、实例范围的方式进行特殊行为的时候。
    • @smci 描述符的整个 point 是为了掩盖这两种情况之间的区别,因此您可以在两者之间来回切换实现而不会破坏您的界面。
    • 好的,我从文档的许多阅读中形成了一个错误的印象,即 a) 属性以某种未定义的方式比属性“更好”(比如在迭代 obj.__dict__ 时说稳定性)和 b)属性和属性不应该随意混合——显然我的印象是错误的。不得不说Ben's 评论比标准文档更清晰。谢谢。
    【解决方案3】:

    我只是将 dict 放在本地范围内并在那里获取/设置我的属性。

    class test(object):
        def __init__(self,**kwargs):
            self.kwargs = kwargs
            #self.value = 20 asign from init is possible
        @property
        def value(self):
            if self.kwargs.get('value') == None:
                self.kwargs.update(value=0)#default
            return self.kwargs.get('value')
        @value.setter
        def value(self,v):
            print(v) #do something with v
            self.kwargs.update(value=v)
    
    x = test()
    print(x.value)
    x.value = 10
    x.value = 5
    

    输出

    0
    10
    5
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-20
      • 1970-01-01
      • 1970-01-01
      • 2022-12-01
      • 2017-10-05
      • 1970-01-01
      • 2016-08-04
      相关资源
      最近更新 更多