【问题标题】:How does __setattr__ work with class attributes?__setattr__ 如何处理类属性?
【发布时间】:2016-09-26 17:15:19
【问题描述】:

我试图了解 __setattr__ 如何与类属性一起工作。当我试图重写 __setattr__ 以防止在简单类中写入属性时,这个问题就出现了。

我第一次尝试使用实例级属性如下:

class SampleClass(object):

    def __init__(self):
        self.PublicAttribute1 = "attribute"

    def __setattr__(self, key, value):
        raise Exception("Attribute is Read-Only")

我原本以为只有外部尝试设置属性才会抛出错误,但即使是 __init__ 里面的语句也会导致抛出异常。

后来我最终找到了另一种方法来解决这个问题,同时尝试解决不同的问题。我最终得到的是:

class SampleClass(object):
    PublicAttribute1 = "attribute"

    def __setattr__(self, key, value):
        raise Exception("Attribute is Read-Only")

我希望得到相同的结果,但令我惊讶的是,我能够设置类属性,同时防止在初始声明后进行更改。

但我不明白为什么会这样。我知道这与类与实例变量有关。我的理论是在类变量上使用 __setattr__ 会创建一个同名的实例变量,因为我相信我认为我以前见过这种行为,但我不确定。

谁能解释一下这里发生了什么?

【问题讨论】:

    标签: python


    【解决方案1】:

    __setattr__() 仅适用于类的实例。在第二个示例中,当您定义 PublicAttribute1 时,您是在类上定义它;没有实例,所以不会调用__setattr__()

    注意在 Python 中,您使用 . 表示法访问的内容称为属性,而不是变量。 (在其他语言中,它们可能被称为“成员变量”或类似名称。)

    如果您在实例上设置同名属性,则类属性将被隐藏是正确的。例如:

    class C(object):
        attr = 42
    
     c = C()
     print(c.attr)     # 42
     c.attr = 13
     print(c.attr)     # 13
     print(C.attr)     # 42
    

    Python 通过首先查看实例来解析属性访问,如果实例上没有该名称的属性,它会查看实例的类,然后是该类的父类,依此类推,直到找到 @987654326 @,Python 类层次结构的根对象。

    所以在上面的例子中,我们在类上定义了attr。因此,当我们访问c.attr(实例属性)时,我们会得到42,即类上的属性值,因为实例上没有这样的属性。当我们设置实例的属性,然后再次打印c.attr 时,我们得到了我们刚刚设置的值,因为现在实例上有一个该名称的属性。但是值42 仍然作为类的属性存在,C.attr,正如我们在第三个print 中看到的那样。

    __init__() 方法中设置实例属性的语句由 Python 处理,就像在对象上设置属性的任何代码一样。 Python 不关心代码是在类“内部”还是“外部”。那么,你可能会想,在初始化对象的时候,怎么绕过__setattr__()的“保护”呢?很简单:你调用一个没有这种保护的类的__setattr__() 方法,通常是你的父类的方法,然后将它传递给你的实例。

    所以不要写:

    self.PublicAttribute1 = "attribute"
    

    你必须写:

     object.__setattr__(self, "PublicAttribute1", "attribute")
    

    由于属性存储在实例的属性字典中,名为__dict__,您也可以通过直接写入__setattr__ 来绕过您的__setattr__

     self.__dict__["PublicAttribute1"] = "attribute"
    

    这两种语法都是丑陋而冗长的,但是你可以相对容易地破坏你试图添加的保护(毕竟,如果你能做到,其他人也能做到)可能会让你得出 Python 的结论对受保护的属性没有很好的支持。事实上它没有,这是设计使然。 “我们都是同意这里的成年人。”你不应该考虑 Python 的公共或私有属性。所有属性都是公开的。 约定使用单个前导下划线命名“私有”属性;这会警告正在使用您的对象的任何人,他们正在弄乱某种实现细节,但如果他们需要并且愿意接受风险,他们仍然可以这样做。

    【讨论】:

    • 感谢您的回答。这个解释正是我想要的。
    【解决方案2】:

    类中定义的__setattr__ 方法仅在类的istances 上的属性分配时调用。不会为类变量调用它,因为它们没有被分配到具有该方法的类的实例上。

    当然,类也是实例。它们是type 的实例(或自定义元类,通常是type 的子类)。所以如果你想阻止类变量的创建,你需要创建一个带有__setattr__方法的元类。

    但这并不是你真正需要让你的班级做你想做的事。要获得只能写入一次的只读属性(在__init__ 方法中),您可能可以使用一些更简单的逻辑来解决问题。一种方法是在__init__ 方法的末尾设置另一个属性,它告诉__setattr__ 在设置后锁定分配:

    class Foo:
        def __init__(self, a, b):
            self.a = a
            self.b = b
            self._initialized = True
    
        def __setattr__(self, name, value):
            if self.__dict__.get('_initialized'):
                raise Exception("Attribute is Read-Only")
            super().__setattr__(name, value)
    

    另一种选择是将property 描述符用于只读属性,并将实际值存储在可以正常分配的“私有”变量中。在这个版本中你不会使用__setattr__

    class Foo():
        def __init__(a, b):
            self._a = a
            self._b = b
    
        @property
        def a(self):
            return self._a
    
        @property
        def b(self):
            return self._b
    

    【讨论】:

    • 我实际上已经尝试过与您的第一个建议非常相似的方法,但是我确实知道 self.__dict__get('initialized') 并且当我尝试设置我的布尔值时最终抛出了错误。我放弃这些类型的方法的另一个原因是,我想在我并不总是拥有类实例的地方在外部使用该属性。不过,这可能不是好的 Python 编程习惯。我对这门语言还比较陌生,所以我不确定。
    【解决方案3】:

    __setattr__ 仅在类的实例 上调用,而不是在实际类本身上调用。但是,类仍然是一个对象,因此在设置类的属性时,会调用该类的类的__setattr__

    要更改在类上设置属性的方式,您需要查看元类。元类对于几乎任何应用程序来说都是多余的,而且很容易让人混淆。尝试将用户定义的类/对象设为只读通常是个坏主意。也就是说,这是一个简单的例子。

    >>> class SampleMetaclass(type): # type as parent, not object
    ...     def __setattr__(self, name, value):
    ...         raise AttributeError("Class is read-only")
    ... 
    >>> class SampleClass(metaclass=SampleMetaclass):
    ...     def __setattr__(self, name, value):
    ...         raise AttributeError("Instance is read-only")
    ... 
    >>> SampleClass.attr = 1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in __setattr__
    AttributeError: Class is read-only
    >>> s = SampleClass()
    >>> s.attr = 1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in __setattr__
    AttributeError: Instance is read-only
    

    【讨论】:

    • 元类的 __setattr__ 仅在将属性绑定到元类(类)的实例时调用,而不是像在 OP 的情况下那样设置类变量时调用
    • @MosesKoledoye 是的。但这有点棘手,因为 Python 需要能够自己设置一些类属性。虽然可行。
    • 您可以在元类的__new__ 中捕获类属性。可能足以检查通过 not attr.startswith('__') 的属性
    • @MosesKoledoye 祝你好运,然后在你的类上定义函数。如果一个类没有函数,那么它的意义就更小了。
    • 我知道,我知道这很复杂,但检查 not callable(attr) 可以解决这个问题。
    【解决方案4】:

    以下可以在python文档中找到

    属性分配和删除会更新实例的字典,而不是类的字典。如果类有 __setattr__() 或 __delattr__() 方法,则调用此方法而不是直接更新实例字典。

    您可以清楚地看到 __setattr__() 在调用时会更改实例字典。因此,每当您尝试分配 self.instance 时,它都会引发您的异常 Exception("Attribute is Read-Only")。 而设置类的属性并非如此,因此不会引发异常。

    【讨论】:

    • 谢谢。这解释了为什么第二个示例有效,但我仍然不明白为什么在第二种情况下,如果我尝试设置类变量它会引发错误。如果我在类的 n 实例上调用 foo.PublicAttribute1 = "newattribute" ,我不是在设置类变量吗?根据文档,这不会绕过 __setattr__() 吗?
    【解决方案5】:

    __setattr__() 每当一个值被分配给任何类的属性时都会被调用。即使属性在__init__() 中初始化,它也会调用__setattr__()。下面是一个例子来说明:

    >>> class X(object):
    ...     def __init__(self, a, b):
    ...         self.a = a
    ...         self.b = b
    ...     def __setattr__(self, k, v):
    ...         print 'Set Attr: {} -> {}'.format(k, v)
    ...
    >>> x = X(1, 3)       # <-- __setattr__() called by __init__() 
    Set Attr: a -> 1
    Set Attr: b -> 3
    >>> x.a = 9           # <-- __setattr__() called during external assignment
    Set Attr: a -> 9
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 2016-03-20
      • 2018-10-02
      • 1970-01-01
      • 1970-01-01
      • 2015-04-26
      • 2011-10-23
      相关资源
      最近更新 更多