我试图了解__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__将创建一个同名的实例变量,因为我相信我之前已经看过这种行为,但我不确定。
有人能解释到底发生了什么吗?
答案 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