【问题标题】:Why does getting a class property from an instance raise an AttributeError?为什么从实例获取类属性会引发 AttributeError?
【发布时间】:2022-01-05 09:30:24
【问题描述】:

通常,您可以从该类的实例访问常规类属性/字段。但是,当尝试访问类 property 时,会引发 AttributeError。为什么实例看不到类对象的属性?

class Meta(type):

    @property
    def cls_prop(cls):
        return True


class A(metaclass=Meta):
    cls_attr = True


A.cls_attr # True
A.cls_prop # True
a = A()
a.cls_attr # True
a.cls_prop # AttributeError: 'A' object has no attribute 'cls_prop'

【问题讨论】:

    标签: python class properties attributeerror metaclass


    【解决方案1】:

    这是由于属性查找的工作方式。在尝试检索 实例中的属性,Python 会这样做:

    1. 调用实例的类__getattribute__(不是元类__getattribute__),这反过来会:

      1. 按照方法解析顺序检查实例的类及其超类的属性。它不会继续到类的类(元类) - 它遵循继承链..

        1. 如果在类中找到该属性并且它具有__get__ 方法,使其成为描述符:以实例及其类作为参数调用__get__ 方法 - 返回值用作属性值
          • 注意:对于使用__slots__ 的类,每个实例属性都记录在一个特殊的描述符中——该描述符存在于类本身中并且有一个__get__ 方法,因此插槽类的实例属性在此检索步骤
        2. 如果没有__get__ 方法,它只是跳过类的搜索。
      2. 检查实例本身:该属性应作为实例__dict__ 属性中的条目存在。如果是,则返回相应的值。 (__dict__ 是一个特殊的属性,可以在 cPython 中直接访问,否则将遵循上面开槽属性的描述符规则)

      3. 再次检查类(及其继承层次结构)的属性,这一次,不管它是否具有__get__ 方法。如果找到,则使用它。类中的此属性检查直接在类及其超类__dict__ 中执行,而不是通过以递归方式调用它们自己的__getattribute__。 (*)

    2. 类(或超类)__getattr__ 方法被调用,如果它存在的话,带有属性名称。它可能会返回一个值,也可能会引发 AttributeError(__getattr__ 与低级 __getattribute__ 不同,更容易自定义)

    3. 引发了AttributeError。

    (*) 这是回答您问题的步骤:不会在元类中搜索实例中的属性。在上面的代码中,如果您尝试使用A.cls_prop 作为属性,而不是A().cls_prop,它将起作用:直接从类中检索属性时,它在上面的检索算法中扮演“实例”的角色。

    (**) 注意。这个属性检索算法描述比较完整,但是对于属性赋值和删除,不是检索,而是一个描述符有一些区别,根据它是否具有__set__(或__del__)方法,使其成为“数据描述符”与否:非数据描述符(例如在实例的类主体中定义的常规方法)直接分配在实例的字典上,因此仅针对该实例覆盖和“关闭”方法。数据描述符将调用其__set__ 方法。

    如何使元类中定义的属性适用于实例:

    如您所见,属性访问是非常可定制的,如果您想在可从实例工作的元类中定义“类属性”,则可以轻松自定义代码以使其正常工作。一种方法是添加到您的基类(而不是元类)中,__getattr__ 将在元类上查找自定义描述符并调用它们:

    class Base(metaclass=Meta):
        def __getattr__(self, name):
            metacls = type(cls:=type(self))
            if hasattr(metacls, name):
                metaattr = getattr(metacls, name)
                if isinstance(metaattr, property):  # customize this check as you want. It is better not to call it for anything that has a `__get__`, as it would retrieve metaclass specific stuff, such as its __init__ and __call__ methods, if those were not defined in the class.
                    attr = metaattr.__get__(cls, metacls)
                    return attr
            return super().__getattr__(name)
    

    和:

    In [44]: class A(Base):
        ...:     pass
        ...:
    
    In [45]: a = A()
    
    In [46]: a.cls_prop
    Out[46]: True
    

    【讨论】:

      【解决方案2】:

      您看到的错误不是因为属性和属性行为之间的任何差异,而是因为您在 Meta 中定义属性和在 A 类中定义属性。如果您在 A 中定义该属性,它将正常工作。

      【讨论】:

      • 如果属性是在class A 中定义的,它将不再是类的属性,而是它的实例的属性。请注意,我也可以在class Meta.__init__ 中定义class_attr,它会正常工作。
      猜你喜欢
      • 1970-01-01
      • 2017-12-09
      • 2012-10-08
      • 2010-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多