在 class' class 方法中调用 super() 以获取 metaclass 方法
Calling super() inside a class' classmethod to get at the metaclass method
就在我理解 metaclasses 的时候...
免责声明:我在发帖前四处寻找答案,但我找到的大部分答案都是关于打电话给 super()
以获得另一个 @classmethod
在 MRO 中(不涉及 metaclass),或者,令人惊讶的是,他们中的很多人试图在 metaclass.__new__
或 metaclass.__call__
中做一些事情,这意味着 class 不是尚未完全创建。我很确定(假设是 97%)这不是这些问题之一。
环境:Python3.7.2
问题:
我有一个 metaclass FooMeta
定义了一个方法 get_foo(cls)
,一个 class Foo
是从那个 metaclass 构建的(所以一个实例FooMeta
) 的 @classmethod
get_bar(cls)
。然后是继承自 Foo
的另一个 class Foo2
。在 Foo2
中,我通过将其声明为 @classmethod
并调用 super()
来子 class get_foo
。这惨遭失败...
即使用此代码
class FooMeta(type):
def get_foo(cls):
return 5
class Foo(metaclass=FooMeta):
@classmethod
def get_bar(cls):
return 3
print(Foo.get_foo)
# >>> <bound method FooMeta.get_foo of <class '__main__.Foo'>>
print(Foo.get_bar)
# >>> <bound method Foo.get_bar of <class '__main__.Foo'>>
class Foo2(Foo):
@classmethod
def get_foo(cls):
print(cls.__mro__)
# >>> (<class '__main__.Foo2'>, <class '__main__.Foo'>, <class 'object'>)
return super().get_foo()
@classmethod
def get_bar(cls):
return super().get_bar()
print(Foo2().get_bar())
# >>> 3
print(Foo2().get_foo())
# >>> AttributeError: 'super' object has no attribute 'get_foo'
问题:
因此,我的 class 是 metaclass 的一个实例,并且已验证 class 方法都存在于 class Foo
上,为什么两者都不存在调用在 Foo2
内部工作的 super().get_***()
? 我对 metaclasses 或 super()
有什么不理解,这会阻止我发现这些结果合乎逻辑?
编辑:进一步测试表明 Foo2
上的方法是 class 方法或实例方法不会改变结果。
编辑 2:感谢@chepner 的回答,我认为问题是 super()
正在返回一个代表 Foo
的超级对象(这已通过 super().__thisclass__
验证),我是期待 super().get_foo()
在幕后表现(甚至可能打电话)get_attr(Foo, 'get_foo')
。好像不是...我还在想为什么,但是越来越清楚了:)
注一
明确一点:
Foo
不继承自FooMeta
.
FooMeta
是 不是 Foo
[=164= 的超级 class ]
super
将不起作用。
注2
既然注释 (1) 已经不在了,如果你想从 metaclass 实例的方法内部访问 metaclass 方法,你可以这样做它像这样:
class FooMeta(type):
_foo = 5
def get_foo(cls):
print("`get_foo` from `FooMeta` was called!")
class Foo(metaclass=FooMeta):
@classmethod
def bar(Foo):
FooMeta = type(Foo)
FooMeta_dot_getfoo = FooMeta.get_foo
FooMeta_dot_getfoo(Foo)
def baz(self):
Foo = type(self)
FooMeta = type(Foo)
FooMeta_dot_getfoo = FooMeta.get_foo
FooMeta_dot_getfoo(Foo)
Foo.bar()
foo = Foo()
foo.baz()
输出为:
`get_foo` from `FooMeta` was called!
`get_foo` from `FooMeta` was called!
注3
如果你有一个class方法与metaclass中的方法同名,为什么metaclass方法NOT 接到电话了吗?考虑以下代码:
class FooMeta(type):
def get_foo(cls):
print("META!")
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(cls):
print("NOT META!")
Foo.get_foo()
输出为NOT META!
在下面的讨论中,假设:
foo
是 Foo
的实例
Foo
是 FooMeta
的实例
第一次在这个post,我会有伪代码,不是python。不要尝试 运行 以下内容。 __getattribute__
大致如下所示:
class FooMeta(type):
def get_foo(Foo):
print("META!")
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(Foo):
print("NOT META!")
def __getattribute__(foo, the string "get_foo"):
try:
attribute = "get_foo" from instance foo
except AttributeError:
attribute = "get_foo" from class Foo
# Begin code for handling "descriptors"
if hasattr(attribute, '__get__'):
attr = attribute.__get__(None, Foo)
# End code for handling "descriptors"
return attribute
foo = Foo()
foo.get_foo() # prints "NOT META!"
get_foo = Foo.__getattribute__(foo, "get_foo")
get_foo.__call__()
您实际上可以忽略“code for handling "descriptors"
”的内容。为了完整起见,我只包括了它。
请注意,__getattribute__
没有说 "get get_foo
from the meta class."
- 首先,我们尝试从实例中获取
get_foo
。也许 get_foo
是一个成员变量。也许一个实例有 get_foo = 1
而另一个实例有 get_foo = 5
计算机不知道。电脑很笨
- 计算机发现实例没有名为
get_foo
的成员变量。然后它说,"ah ha! I bet that get_foo
belongs to the CLASS." 所以,它看起来在那里,瞧瞧,它就在那里:Foo
有一个名为 get_foo
的属性。 FooMeta
也有一个名为 get_foo
的属性,但谁在乎那个。
需要重点关注的是:
Foo
有一个名为 get_foo
的属性
MetaFoo
有一个名为 get_foo
的属性
他们两者都有名为get_foo
的属性,但是Foo
和MetaFoo
是不同的对象。这两个 get_foo
并不是共享的。我可以有 obj1.x = 1
和 obj2.x = 99
。没问题。
FooMeta
有自己的 __getattribute__
方法。之前说了Foo.__getattribute__
,现在说说MeTa__getattribute__
class FooMeta(type):
def get_foo(Foo):
print("META!")
def __getattribute__(Foo, the string "get_foo"):
try: # LINE 1
attribute = "get_foo" from class Foo # LINE 2
except AttributeError: # LINE 3
attribute = "get_foo" from class FooMeta # LINE 4
# LINE 5
# Begin code for handling "descriptors"
if hasattr(attribute, '__get__'):
attr = attribute.__get__(None, Foo)
# End code for handling "descriptors"
return attribute
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(Foo):
print("NOT META!")
Foo.get_foo()
get_foo = FooMeta.__getattribute__(Foo, "get_foo")
get_foo.__call__()
事件顺序:
- 第 1 行和第 2 行发生
- 第 3、4 和 5 行不会发生
- 你可以忽略关于描述符的东西,因为这个问题中不同的
get_foo
中的 none 有一个 __get__
方法
好了!为什么只有第 1 和第 2 行?因为你把@classmethod
弄傻了!我们检查 Foo
看它是否有 get_foo
并且确实有!如果我们首先找到实例属性,为什么要检查 class 属性?在检查是否可能发生之前,我们总是检查属性是否属于实例 (Foo
) first-and-foremost仅是属于 class (FooMeta
) 并由所有实例共享的静态成员变量的一个副本。
请注意,如果 Foo 没有 get_foo
,那么 FooMeta.__getattribute__(Foo, "get_foo")
将从 metaclass return get_foo
因为第一次尝试(获取它来自实例)失败。您通过为实例提供与 class 的静态成员变量同名的内容来阻止该选项。
class K:
im_supposed_to_be_shared = 1
def __init__(self, x):
# NOPE!
self.im_supposed_to_be_shared = x
# maybe try type(self)
obj1 = K(14)
obj2 = K(29)
print(obj1.im_supposed_to_be_shared)
print(obj2.im_supposed_to_be_shared)
print(K.im_supposed_to_be_shared)
打印:
14
29
1
不打印:
29
29
29
注意,如果要设置一个静态的class成员变量,instance.mem_var = 5
是一个很ⱽᵉᴿʸ的坏主意。您将给 instance
一个新的成员变量,而 class 静态(共享)成员变量将被隐藏。你可以用这样的东西来解决这个问题:
def __setattr__(self, attr_name, attr_val):
if hasattr(type(self), attr_name):
setattr(type(self), attr_name, attr_val)
else:
super_class = inspect.getmro(type(self))[1]
super_class.__setattr__(self, attr_name, attr_val)
然后你的 lil' compy 将打印:
29
29
29
注4
class Foo:
@classmethod
def funky(cls):
pass
是不是MetaClass.funky = funky
。相反,它是:
def funky(cls)
pass
Funky = classmethod (funky)
... 几乎等同于:
def funky(cls):
pass
funky = lambda self, *args, **kwargs: funky(type(self), *args, **kwargs)
note 4 故事的寓意是 classmethod
funky
是 Foo
的属性而不是 FooMeta
的属性
get_foo
不是 Foo
的属性,而是 type(Foo)
的属性:
>>> 'get_foo' in Foo.__dict__
False
>>> 'get_foo' in type(Foo).__dict__
True
因此,虽然 Foo.get_foo
将解析为 type(Foo).get_foo
,但 super().get_foo
不会,因为 super()
返回的代理类似于 Foo
,但不是Foo
本身。
Foo
可能有一个 get_foo
方法,但是 super
不是用来检查超类有哪些属性的。 super
关心哪些属性 源自 超类。
要理解 super
的设计,请考虑以下多重继承层次结构:
class A:
@classmethod
def f(cls):
return 1
class B(A):
pass
class C(A):
@classmethod
def f(cls):
return 2
class D(B, C):
@classmethod
def f(cls):
return super().f() + 1
A、B、C、D都有一个f
类方法,但是B的f
是继承自A的。D的方法解析顺序,类检查的顺序属性查找,进入 (D, B, C, A, object)
.
super().f() + 1
在 cls
的 MRO 中搜索 f
实现。它应该找到的是 C.f
,但 B 具有继承的 f
实现,并且 B 在 MRO 中位于 C 之前。如果 super
选择 B.f
,这将破坏 C
在通常称为 "diamond problem" 的情况下覆盖 f
的尝试。 =44=]
super
不查看 B 具有哪些属性,而是直接查看 B 的 __dict__
,因此它只考虑 B
而不是 B
实际提供的属性的 super类 或 meta类.
现在,回到您的 get_foo
/get_bar
情况。 get_bar
来自 Foo
本身,所以 super().get_bar()
找到 Foo.get_bar
。但是,get_foo
不是由 Foo
提供的,而是由 FooMeta
元类提供的,并且 Foo.__dict__
中没有 get_foo
的条目。因此,super().get_foo()
什么也找不到。
obj.attr
调用type(obj).__getattribute__(obj, 'attr')
,即
- a)
object.__getattribute__(obj, 'attr')
,它在 obj
本身以及 obj
的 class 及其 [=78] 中查找 'attr'
=];或
- b)
type.__getattribute__(obj, 'attr')
如果 obj
是一个 type
实例,它在 obj
本身及其 parents 中查找 'attr'
,以及 obj
的 class 及其 parents;或
- c)
super.__getattribute__(obj, 'attr')
如果 obj
是一个 super
实例,
- c.1) 在
instance
的 class 中查找 'attr'
及其 cls
之后的 parents,如果 obj
是 super(cls, instance)
,或
- c.2) 在
subclass
本身及其 parents 过去 cls
中查找 'attr'
,如果 obj
是 super(cls, subclass)
.
当你在class方法Foo2.get_foo
中调用super().get_foo()
时,相当于调用super(Foo2, cls).get_foo()
,你是c.2)的情况,即你是在 cls
本身及其 parents 过去 Foo2
中查找 'get_foo'
,即您在 Foo
中查找 'get_foo'
。这就是调用失败的原因。
您希望 class 方法 Foo2.get_foo
中的调用 super().get_foo()
成功,因为您认为它等同于案例 b) 中的调用 Foo.get_foo()
,也就是说,您认为您正在 cls
本身及其 parents、 以及 Foo
的 class 中查找 'get_foo'
(FooMeta
) 及其 parents.
就在我理解 metaclasses 的时候...
免责声明:我在发帖前四处寻找答案,但我找到的大部分答案都是关于打电话给 super()
以获得另一个 @classmethod
在 MRO 中(不涉及 metaclass),或者,令人惊讶的是,他们中的很多人试图在 metaclass.__new__
或 metaclass.__call__
中做一些事情,这意味着 class 不是尚未完全创建。我很确定(假设是 97%)这不是这些问题之一。
环境:Python3.7.2
问题:
我有一个 metaclass FooMeta
定义了一个方法 get_foo(cls)
,一个 class Foo
是从那个 metaclass 构建的(所以一个实例FooMeta
) 的 @classmethod
get_bar(cls)
。然后是继承自 Foo
的另一个 class Foo2
。在 Foo2
中,我通过将其声明为 @classmethod
并调用 super()
来子 class get_foo
。这惨遭失败...
即使用此代码
class FooMeta(type):
def get_foo(cls):
return 5
class Foo(metaclass=FooMeta):
@classmethod
def get_bar(cls):
return 3
print(Foo.get_foo)
# >>> <bound method FooMeta.get_foo of <class '__main__.Foo'>>
print(Foo.get_bar)
# >>> <bound method Foo.get_bar of <class '__main__.Foo'>>
class Foo2(Foo):
@classmethod
def get_foo(cls):
print(cls.__mro__)
# >>> (<class '__main__.Foo2'>, <class '__main__.Foo'>, <class 'object'>)
return super().get_foo()
@classmethod
def get_bar(cls):
return super().get_bar()
print(Foo2().get_bar())
# >>> 3
print(Foo2().get_foo())
# >>> AttributeError: 'super' object has no attribute 'get_foo'
问题:
因此,我的 class 是 metaclass 的一个实例,并且已验证 class 方法都存在于 class Foo
上,为什么两者都不存在调用在 Foo2
内部工作的 super().get_***()
? 我对 metaclasses 或 super()
有什么不理解,这会阻止我发现这些结果合乎逻辑?
编辑:进一步测试表明 Foo2
上的方法是 class 方法或实例方法不会改变结果。
编辑 2:感谢@chepner 的回答,我认为问题是 super()
正在返回一个代表 Foo
的超级对象(这已通过 super().__thisclass__
验证),我是期待 super().get_foo()
在幕后表现(甚至可能打电话)get_attr(Foo, 'get_foo')
。好像不是...我还在想为什么,但是越来越清楚了:)
注一
明确一点:
Foo
不继承自FooMeta
.FooMeta
是 不是Foo
[=164= 的超级 class ]
super
将不起作用。
注2
既然注释 (1) 已经不在了,如果你想从 metaclass 实例的方法内部访问 metaclass 方法,你可以这样做它像这样:
class FooMeta(type):
_foo = 5
def get_foo(cls):
print("`get_foo` from `FooMeta` was called!")
class Foo(metaclass=FooMeta):
@classmethod
def bar(Foo):
FooMeta = type(Foo)
FooMeta_dot_getfoo = FooMeta.get_foo
FooMeta_dot_getfoo(Foo)
def baz(self):
Foo = type(self)
FooMeta = type(Foo)
FooMeta_dot_getfoo = FooMeta.get_foo
FooMeta_dot_getfoo(Foo)
Foo.bar()
foo = Foo()
foo.baz()
输出为:
`get_foo` from `FooMeta` was called!
`get_foo` from `FooMeta` was called!
注3
如果你有一个class方法与metaclass中的方法同名,为什么metaclass方法NOT 接到电话了吗?考虑以下代码:
class FooMeta(type):
def get_foo(cls):
print("META!")
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(cls):
print("NOT META!")
Foo.get_foo()
输出为NOT META!
在下面的讨论中,假设:
foo
是Foo
的实例
Foo
是FooMeta
的实例
第一次在这个post,我会有伪代码,不是python。不要尝试 运行 以下内容。 __getattribute__
大致如下所示:
class FooMeta(type):
def get_foo(Foo):
print("META!")
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(Foo):
print("NOT META!")
def __getattribute__(foo, the string "get_foo"):
try:
attribute = "get_foo" from instance foo
except AttributeError:
attribute = "get_foo" from class Foo
# Begin code for handling "descriptors"
if hasattr(attribute, '__get__'):
attr = attribute.__get__(None, Foo)
# End code for handling "descriptors"
return attribute
foo = Foo()
foo.get_foo() # prints "NOT META!"
get_foo = Foo.__getattribute__(foo, "get_foo")
get_foo.__call__()
您实际上可以忽略“code for handling "descriptors"
”的内容。为了完整起见,我只包括了它。
请注意,__getattribute__
没有说 "get get_foo
from the meta class."
- 首先,我们尝试从实例中获取
get_foo
。也许get_foo
是一个成员变量。也许一个实例有get_foo = 1
而另一个实例有get_foo = 5
计算机不知道。电脑很笨 - 计算机发现实例没有名为
get_foo
的成员变量。然后它说,"ah ha! I bet thatget_foo
belongs to the CLASS." 所以,它看起来在那里,瞧瞧,它就在那里:Foo
有一个名为get_foo
的属性。FooMeta
也有一个名为get_foo
的属性,但谁在乎那个。
需要重点关注的是:
Foo
有一个名为get_foo
的属性
MetaFoo
有一个名为get_foo
的属性
他们两者都有名为get_foo
的属性,但是Foo
和MetaFoo
是不同的对象。这两个 get_foo
并不是共享的。我可以有 obj1.x = 1
和 obj2.x = 99
。没问题。
FooMeta
有自己的 __getattribute__
方法。之前说了Foo.__getattribute__
,现在说说MeTa__getattribute__
class FooMeta(type):
def get_foo(Foo):
print("META!")
def __getattribute__(Foo, the string "get_foo"):
try: # LINE 1
attribute = "get_foo" from class Foo # LINE 2
except AttributeError: # LINE 3
attribute = "get_foo" from class FooMeta # LINE 4
# LINE 5
# Begin code for handling "descriptors"
if hasattr(attribute, '__get__'):
attr = attribute.__get__(None, Foo)
# End code for handling "descriptors"
return attribute
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(Foo):
print("NOT META!")
Foo.get_foo()
get_foo = FooMeta.__getattribute__(Foo, "get_foo")
get_foo.__call__()
事件顺序:
- 第 1 行和第 2 行发生
- 第 3、4 和 5 行不会发生
- 你可以忽略关于描述符的东西,因为这个问题中不同的
get_foo
中的 none 有一个__get__
方法
好了!为什么只有第 1 和第 2 行?因为你把@classmethod
弄傻了!我们检查 Foo
看它是否有 get_foo
并且确实有!如果我们首先找到实例属性,为什么要检查 class 属性?在检查是否可能发生之前,我们总是检查属性是否属于实例 (Foo
) first-and-foremost仅是属于 class (FooMeta
) 并由所有实例共享的静态成员变量的一个副本。
请注意,如果 Foo 没有 get_foo
,那么 FooMeta.__getattribute__(Foo, "get_foo")
将从 metaclass return get_foo
因为第一次尝试(获取它来自实例)失败。您通过为实例提供与 class 的静态成员变量同名的内容来阻止该选项。
class K:
im_supposed_to_be_shared = 1
def __init__(self, x):
# NOPE!
self.im_supposed_to_be_shared = x
# maybe try type(self)
obj1 = K(14)
obj2 = K(29)
print(obj1.im_supposed_to_be_shared)
print(obj2.im_supposed_to_be_shared)
print(K.im_supposed_to_be_shared)
打印:
14
29
1
不打印:
29
29
29
注意,如果要设置一个静态的class成员变量,instance.mem_var = 5
是一个很ⱽᵉᴿʸ的坏主意。您将给 instance
一个新的成员变量,而 class 静态(共享)成员变量将被隐藏。你可以用这样的东西来解决这个问题:
def __setattr__(self, attr_name, attr_val):
if hasattr(type(self), attr_name):
setattr(type(self), attr_name, attr_val)
else:
super_class = inspect.getmro(type(self))[1]
super_class.__setattr__(self, attr_name, attr_val)
然后你的 lil' compy 将打印:
29
29
29
注4
class Foo:
@classmethod
def funky(cls):
pass
是不是MetaClass.funky = funky
。相反,它是:
def funky(cls)
pass
Funky = classmethod (funky)
... 几乎等同于:
def funky(cls):
pass
funky = lambda self, *args, **kwargs: funky(type(self), *args, **kwargs)
note 4 故事的寓意是 classmethod
funky
是 Foo
的属性而不是 FooMeta
get_foo
不是 Foo
的属性,而是 type(Foo)
的属性:
>>> 'get_foo' in Foo.__dict__
False
>>> 'get_foo' in type(Foo).__dict__
True
因此,虽然 Foo.get_foo
将解析为 type(Foo).get_foo
,但 super().get_foo
不会,因为 super()
返回的代理类似于 Foo
,但不是Foo
本身。
Foo
可能有一个 get_foo
方法,但是 super
不是用来检查超类有哪些属性的。 super
关心哪些属性 源自 超类。
要理解 super
的设计,请考虑以下多重继承层次结构:
class A:
@classmethod
def f(cls):
return 1
class B(A):
pass
class C(A):
@classmethod
def f(cls):
return 2
class D(B, C):
@classmethod
def f(cls):
return super().f() + 1
A、B、C、D都有一个f
类方法,但是B的f
是继承自A的。D的方法解析顺序,类检查的顺序属性查找,进入 (D, B, C, A, object)
.
super().f() + 1
在 cls
的 MRO 中搜索 f
实现。它应该找到的是 C.f
,但 B 具有继承的 f
实现,并且 B 在 MRO 中位于 C 之前。如果 super
选择 B.f
,这将破坏 C
在通常称为 "diamond problem" 的情况下覆盖 f
的尝试。 =44=]
super
不查看 B 具有哪些属性,而是直接查看 B 的 __dict__
,因此它只考虑 B
而不是 B
实际提供的属性的 super类 或 meta类.
现在,回到您的 get_foo
/get_bar
情况。 get_bar
来自 Foo
本身,所以 super().get_bar()
找到 Foo.get_bar
。但是,get_foo
不是由 Foo
提供的,而是由 FooMeta
元类提供的,并且 Foo.__dict__
中没有 get_foo
的条目。因此,super().get_foo()
什么也找不到。
obj.attr
调用type(obj).__getattribute__(obj, 'attr')
,即
- a)
object.__getattribute__(obj, 'attr')
,它在obj
本身以及obj
的 class 及其 [=78] 中查找'attr'
=];或 - b)
type.__getattribute__(obj, 'attr')
如果obj
是一个type
实例,它在obj
本身及其 parents 中查找'attr'
,以及obj
的 class 及其 parents;或 - c)
super.__getattribute__(obj, 'attr')
如果obj
是一个super
实例,- c.1) 在
instance
的 class 中查找'attr'
及其cls
之后的 parents,如果obj
是super(cls, instance)
,或 - c.2) 在
subclass
本身及其 parents 过去cls
中查找'attr'
,如果obj
是super(cls, subclass)
.
- c.1) 在
当你在class方法Foo2.get_foo
中调用super().get_foo()
时,相当于调用super(Foo2, cls).get_foo()
,你是c.2)的情况,即你是在 cls
本身及其 parents 过去 Foo2
中查找 'get_foo'
,即您在 Foo
中查找 'get_foo'
。这就是调用失败的原因。
您希望 class 方法 Foo2.get_foo
中的调用 super().get_foo()
成功,因为您认为它等同于案例 b) 中的调用 Foo.get_foo()
,也就是说,您认为您正在 cls
本身及其 parents、 以及 Foo
的 class 中查找 'get_foo'
(FooMeta
) 及其 parents.