__setattr__如何使用类属性?

时间:2016-09-26 17:15:19

标签: python

我试图了解__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")

我希望得到相同的结果,但对于我的surpirse,我能够设置class属性,同时防止在初始声明后进行更改。

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

有人能解释到底发生了什么吗?

5 个答案:

答案 0 :(得分:4)

类中定义的__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描述符用于只读属性,并将实际值存储在" private"可以正常分配的变量。您未在此版本中使用__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

答案 1 :(得分:4)

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

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

如果在实例上设置了相同名称的属性,那么您的类属性将被遮蔽是正确的。例如:

class C(object):
    attr = 42

 c = C()
 print(c.attr)     # 42
 c.attr = 13
 print(c.attr)     # 13
 print(C.attr)     # 42

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

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

print方法中设置实例属性的语句由Python处理,就像在对象上设置属性的任何代码一样。 Python并不关心代码是在类的“内部”还是“外部”。所以,您可能想知道,在初始化对象时如何绕过__init__()的“保护”?简单:您调用没有该保护的类的__setattr__()方法,通常是您的父类的方法,并将其传递给您的实例。

所以不要写:

__setattr__()

你必须写:

self.PublicAttribute1 = "attribute"

由于属性存储在名为 object.__setattr__(self, "PublicAttribute1", "attribute") 的实例的属性字典中,您还可以通过直接写入__dict__来绕过__setattr__

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

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

答案 2 :(得分: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

答案 3 :(得分:2)

以下内容可以在python文档中找到

  

属性赋值和删除更新实例的字典,而不是类的字典。如果该类具有__setattr __()或__delattr __()方法,则调用此方法而不是直接更新实例字典。

您可以清楚地看到 __ setattr __()在调用实例字典时会更改它。因此,每当您尝试分配 self.instance 时,它都会引发您的异常异常(&#34;属性为只读&#34;)。 鉴于设置类的属性并非如此,因此不会引发异常。

答案 4 :(得分:0)

只要将值分配给任何类的属性,就会调用

__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