python __setattr__如何处理类属性?

rks48beu  于 2023-01-16  发布在  Python
关注(0)|答案(5)|浏览(131)

我试图确切地理解__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")

我期望得到相同的结果,但令我惊讶的是,我能够设置class属性,同时防止在初始声明之后进行更改。
我不明白为什么会这样。我知道这与类和示例变量有关。我的理论是在类变量上使用__setattr__会创建同名的示例变量,因为我相信我以前见过这种行为,但我不确定。
有人能解释一下到底发生了什么吗?

dw1jzc5e

dw1jzc5e1#

__setattr__()只适用于类的示例。在第二个示例中,当您定义PublicAttribute1时,您是在类上定义它;没有示例,所以__setattr__()不会被调用.
注意:在Python中,使用.表示法访问的对象被称为属性,而不是变量(在其他语言中,它们可能被称为“成员变量”或类似的名称)。
如果在示例上设置同名属性,class属性将被隐藏,这是正确的。例如:

class C(object):
    attr = 42

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

Python通过首先查看示例来解决属性访问问题,如果示例上没有该名称的属性,它会查看示例的类,然后是该类的父类,依此类推,直到到达object,Python类层次结构的根对象。
所以在上面的例子中,我们在类上定义了attr。因此,当我们访问c.attr时(示例属性),我们得到类上属性的值42,因为示例上没有这个属性,当我们设置示例的属性,然后再次打印c.attr,我们得到刚才设置的值,因为在示例中有一个属性使用这个名字,但是值42仍然作为类C.attr的属性存在,正如我们看到的第三个print
__init__()方法中设置instance属性的语句由Python处理,就像任何设置对象属性的代码一样。Python不关心代码是在类的“内部”还是“外部”。所以,你可能会想,在初始化对象时,如何绕过__setattr__()的“保护”?简单:你调用一个类的__setattr__()方法,而这个类没有这个保护,通常是你的父类的方法,然后把你的示例传递给它.
所以不要写:

self.PublicAttribute1 = "attribute"

你必须写:

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

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

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

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

ylamdve6

ylamdve62#

类中定义的__setattr__方法只在类的 instances 上的属性赋值时调用,而不是在类变量上调用,因为它们没有在类的示例上用该方法赋值。
当然,类也是示例,它们是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描述符来描述只读属性,并将真实的值存储在可以正常赋值的“私有”变量中。

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

    @property
    def a(self):
        return self._a

    @property
    def b(self):
        return self._b

foo = Foo(3, 5)
foo.a = 7 # causes "AttributeError: can't set attribute"
gev0vcfq

gev0vcfq3#

__setattr__只在类的 instances 上被调用,而不是实际的类本身,但是类仍然是一个对象,所以在设置类的属性时调用类的类的__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
sd2nnvve

sd2nnvve4#

以下内容可在python文档中找到
属性赋值和删除只更新示例的字典,而不是类的字典。如果类有__setattr__()或__delattr__()方法,则调用该方法,而不是直接更新示例字典。
正如你可以清楚地看到的,setattr() 在被调用时改变了示例字典,所以每当你试图分配一个self.instance时,它就会引发异常Exception(“Attribute is Read-Only”),而设置一个类的属性就不会引发异常。

68de4m5k

68de4m5k5#

只要给类的属性赋值,就调用__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

相关问题