为什么从实例中获取 class 属性 会引发 AttributeError?
Why does getting a class property from an instance raise an AttributeError?
通常,您可以从 class 的实例访问常规 class attribute/field。但是,当尝试访问 class 属性 时,会引发 AttributeError。为什么实例看不到 class 对象上的 属性?
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'
您看到的错误不是因为属性和 属性 行为之间的任何差异,而是因为您在 Meta 中定义了 属性,在 A class 中定义了属性。如果您在 A 中定义 属性 它将正常工作。
这是由于属性查找的工作方式所致。在试图检索一个
实例中的属性,Python 做:
调用实例的class__getattribute__
(不是元class__getattribute__
),这反过来会:
检查实例的 class 及其超classes 是否遵循方法解析顺序,以获取属性。它不会继续到 class 的 class(metaclass)——它遵循继承链..
- 如果属性在 class 中找到并且它有一个
__get__
方法,使其成为一个描述符: __get__
方法与实例及其 class 作为参数 - returned 值用作属性值
- 注意:对于使用
__slots__
的classes,每个实例属性都记录在一个特殊的描述符中——它存在于class本身并有一个 __get__
方法,因此在这一步 检索开槽 classes 的实例属性
- 如果没有
__get__
方法,它会跳过 class 处的搜索。
检查实例本身:该属性应作为实例 __dict__
属性中的条目存在。如果是,对应的值是returned。 (__dict__
是一个特殊的属性,可以直接在 cPython 中访问,但否则会遵循上面的开槽属性的描述符规则)
再次检查 class(及其继承层次结构)的属性,这次,不管它是否具有 __get__
方法。如果找到,则使用它。 class 中的此属性检查直接在 class 及其超 classes __dict__
中执行,而不是通过以递归方式调用它们自己的 __getattribute__
。 (*)
class(或 superclasses)__getattr__
方法被调用,如果它存在,带有属性名。可能return一个值,或者引发AttributeError(__getattr__
与低级__getattribute__
不同,更容易定制)
引发了 AttributeError。
(*) 这是回答您问题的步骤:未在元class 中搜索实例中的属性。在上面的代码中,如果您尝试将 A.cls_prop
用作 属性,而不是 A().cls_prop
,它将起作用:当直接从 class 检索属性时,它需要“实例”在上述检索算法中的作用。
(**) 注意。这个属性检索算法描述的比较完整,但是对于属性的赋值和删除,而不是检索,描述符有一些区别,基于它是否具有__set__
(或__del__
)方法,使得它是否是“数据描述符”:非数据描述符(例如在实例的 class 主体中定义的常规方法)直接分配给实例的字典,因此覆盖和“关闭”方法只是为了那个例子。数据描述符将调用其 __set__
方法。
如何使元class中定义的属性适用于实例:
如您所见,属性访问是非常可定制的,如果您想在元class 中定义“class 属性”,该元class 将在实例中运行,则很容易定制你的代码,以便它工作。一种方法是添加到你的 baseclass(不是 metaclass),一个 __getattr__
将在 metaclass 上查找自定义描述符并调用它们:
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
通常,您可以从 class 的实例访问常规 class attribute/field。但是,当尝试访问 class 属性 时,会引发 AttributeError。为什么实例看不到 class 对象上的 属性?
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'
您看到的错误不是因为属性和 属性 行为之间的任何差异,而是因为您在 Meta 中定义了 属性,在 A class 中定义了属性。如果您在 A 中定义 属性 它将正常工作。
这是由于属性查找的工作方式所致。在试图检索一个 实例中的属性,Python 做:
调用实例的class
__getattribute__
(不是元class__getattribute__
),这反过来会:检查实例的 class 及其超classes 是否遵循方法解析顺序,以获取属性。它不会继续到 class 的 class(metaclass)——它遵循继承链..
- 如果属性在 class 中找到并且它有一个
__get__
方法,使其成为一个描述符:__get__
方法与实例及其 class 作为参数 - returned 值用作属性值- 注意:对于使用
__slots__
的classes,每个实例属性都记录在一个特殊的描述符中——它存在于class本身并有一个__get__
方法,因此在这一步 检索开槽 classes 的实例属性
- 注意:对于使用
- 如果没有
__get__
方法,它会跳过 class 处的搜索。
- 如果属性在 class 中找到并且它有一个
检查实例本身:该属性应作为实例
__dict__
属性中的条目存在。如果是,对应的值是returned。 (__dict__
是一个特殊的属性,可以直接在 cPython 中访问,但否则会遵循上面的开槽属性的描述符规则)再次检查 class(及其继承层次结构)的属性,这次,不管它是否具有
__get__
方法。如果找到,则使用它。 class 中的此属性检查直接在 class 及其超 classes__dict__
中执行,而不是通过以递归方式调用它们自己的__getattribute__
。 (*)
class(或 superclasses)
__getattr__
方法被调用,如果它存在,带有属性名。可能return一个值,或者引发AttributeError(__getattr__
与低级__getattribute__
不同,更容易定制)引发了 AttributeError。
(*) 这是回答您问题的步骤:未在元class 中搜索实例中的属性。在上面的代码中,如果您尝试将 A.cls_prop
用作 属性,而不是 A().cls_prop
,它将起作用:当直接从 class 检索属性时,它需要“实例”在上述检索算法中的作用。
(**) 注意。这个属性检索算法描述的比较完整,但是对于属性的赋值和删除,而不是检索,描述符有一些区别,基于它是否具有__set__
(或__del__
)方法,使得它是否是“数据描述符”:非数据描述符(例如在实例的 class 主体中定义的常规方法)直接分配给实例的字典,因此覆盖和“关闭”方法只是为了那个例子。数据描述符将调用其 __set__
方法。
如何使元class中定义的属性适用于实例:
如您所见,属性访问是非常可定制的,如果您想在元class 中定义“class 属性”,该元class 将在实例中运行,则很容易定制你的代码,以便它工作。一种方法是添加到你的 baseclass(不是 metaclass),一个 __getattr__
将在 metaclass 上查找自定义描述符并调用它们:
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