[英]Calling super() inside a class' classmethod to get at the metaclass method
Just when I though I understood metaclasses...就在我虽然理解元类的时候......
Disclaimer : I have looked around for an answer before posting, but most of the answers I have found are about calling super()
to get at another @classmethod
in the MRO (no metaclass involved) or, surprisingly, a lot of them were about trying to do something in metaclass.__new__
or metaclass.__call__
which meant the class wasn't fully created yet.免责声明:我在发布之前已经四处寻找答案,但我发现的大多数答案都是关于调用
super()
以获取 MRO 中的另一个@classmethod
(不涉及元类),或者令人惊讶的是,其中很多都是关于试图在metaclass.__new__
或metaclass.__call__
中做某事,这意味着 class 尚未完全创建。 I'm pretty sure (let's say 97%) that this is not one of those problems.我很确定(比如说 97%)这不是这些问题之一。
Environment : Python 3.7.2环境:Python 3.7.2
The problem : I have a metaclass FooMeta
that defines a method get_foo(cls)
, a class Foo
that is built from that metaclass (so an instance of FooMeta
) and has a @classmethod
get_bar(cls)
.问题:我有一个元类
FooMeta
,它定义了一个方法get_foo(cls)
,一个 class Foo
,它是从该元类构建的(因此是FooMeta
的一个实例)并且有一个@classmethod
get_bar(cls)
。 Then another class Foo2
that inherits from Foo
.然后是另一个继承自
Foo
的 class Foo2
。 In Foo2
, I subclass get_foo
by declaring it a @classmethod
and calling super()
.在
Foo2
中,我通过将get_foo
声明为@classmethod
并调用super()
来继承它。 This fails miserably...这失败得很惨……
ie with this code即使用此代码
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'
The question : So with my class being an instance of the metaclass, and having verified that both class methods exist on the class Foo
, why aren't both calls to the super().get_***()
working inside Foo2
?问题:所以我的 class 是元类的一个实例,并且已经验证了 class 方法都存在于
Foo2
Foo
中,为什么两个调用都不能在super().get_***()
内部工作? What am I not understanding about metaclasses or super()
that's preventing me from finding these results logical?我对元类或
super()
有什么不了解的地方,这会阻止我发现这些结果是合乎逻辑的?
EDIT: Further testing shows that the methods on Foo2
being class methods or instance methods doesn't change the result.编辑:进一步的测试表明,
Foo2
上的方法是 class 方法或实例方法不会改变结果。
EDIT 2: Thanks to @chepner's answer, I think the problem was that super()
was returning a super object representing Foo
(this is verified with super().__thisclass__
) and I was expecting super().get_foo()
to behave (maybe even to call) get_attr(Foo, 'get_foo')
behind the scene.编辑2:感谢@chepner的回答,我认为问题在于
super()
正在返回一个代表Foo
的超级 object (这已通过super().__thisclass__
验证),我期待super().get_foo()
表现(甚至可能在幕后调用) get_attr(Foo, 'get_foo')
。 It seems that it isn't... I'm still wondering why, but it is getting clearer:)好像不是……我还在想为什么,但是越来越清楚了:)
Foo
may have a get_foo
method, but super
isn't designed to check what attributes a superclass has. Foo
可能有一个get_foo
方法,但super
并非旨在检查超类具有哪些属性。 super
cares about what attributes originate in a superclass. super
关心超类中的哪些属性。
To understand super
's design, consider the following multiple inheritance hierarchy:要了解
super
的设计,请考虑以下多个 inheritance 层次结构:
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, and D all have an f
classmethod, but B's f
is inherited from A. D's method resolution order, the sequence of classes checked for attribute lookup, goes (D, B, C, A, object)
. A、B、Z0D61F8370CAD1D412F80B84D1D412F80B84D143E1257Z、D都有一个
f
类方法,但是B的f
继承自A.D的方法解析顺序,检查属性查找的类顺序,为(D, B, C, A, object)
。
super().f() + 1
searches the MRO of cls
for an f
implementation. super().f() + 1
在cls
的 MRO 中搜索f
实现。 The one it should find is C.f
, but B has an inherited f
implementation, and B is before C in the MRO.它应该找到的是
C.f
,但 B 具有继承的f
实现,并且 B 在 MRO 中的 C 之前。 If super
were to pick up Bf
, this would break C
's attempt to override f
, in a situation commonly referred to as the "diamond problem".如果
super
选择Bf
,这将破坏C
覆盖f
的尝试,这种情况通常被称为“钻石问题”。
Instead of looking at what attributes B has, super
looks directly in B's __dict__
, so it only considers attributes actually provided by B
instead of by B
's superclasses or metaclasses. super
不是查看 B 有什么属性,而是直接查看 B 的__dict__
,因此它只考虑B
实际提供的属性,而不是B
的超类或元类。
Now, back to your get_foo
/ get_bar
situation.现在,回到你的
get_foo
/ get_bar
情况。 get_bar
comes from Foo
itself, so super().get_bar()
finds Foo.get_bar
. get_bar
来自Foo
本身,因此super().get_bar()
找到Foo.get_bar
。 However, get_foo
is provided not by Foo
, but by the FooMeta
metaclass, and there is no entry for get_foo
in Foo.__dict__
.但是,
get_foo
不是由Foo
提供的,而是由FooMeta
元类提供的,并且Foo.__dict__
中没有get_foo
的条目。 Thus, super().get_foo()
finds nothing.因此,
super().get_foo()
什么也没找到。
Just to be clear:只是要清楚:
Foo
does not inherit from FooMeta
. Foo
不继承自FooMeta
。FooMeta
is not a super-class of Foo
FooMeta
不是Foo
的超类super
will not work. super
不行。
Now that note (1) is out of the way, if you want to access a metaclass method from inside of a method of an instance of the metaclass, you can do it like this:现在注释 (1) 已经不碍事了,如果你想从元类实例的方法内部访问元类方法,你可以这样做:
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()
The output is: output 是:
`get_foo` from `FooMeta` was called!
`get_foo` from `FooMeta` was called!
If you have a classmethod with the same name as a method in the metaclass, why does the metaclass method NOT get called?如果您有一个与元类中的方法同名的类方法,为什么不调用元类方法? Consider the following code:
考虑以下代码:
class FooMeta(type):
def get_foo(cls):
print("META!")
class Foo(metaclass=FooMeta):
@classmethod
def get_foo(cls):
print("NOT META!")
Foo.get_foo()
The output is NOT META!
output
NOT META!
In the following discussion, assume that:在以下讨论中,假设:
foo
is instance of Foo
foo
是Foo
的实例Foo
is instance of FooMeta
Foo
是FooMeta
的实例For the first time in this post, I will have pseudo code, not python.在这篇文章中,我将第一次使用伪代码,而不是 python。 Don't try to run the following.
不要尝试运行以下命令。
__getattribute__
sorta looks like the following: __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__()
You can actually ignore the stuff which says, " code for handling "descriptors"
."你实际上可以忽略那些说“
code for handling "descriptors"
。” I only included that for completeness.我只是为了完整性而将其包括在内。
Note that nowhere does __getattribute__
say, "get get_foo
from the meta class."请注意,
__getattribute__
getattribute__ 没有说“从元 class 中获取get_foo
”。
get_foo
from the instance.get_foo
。 Maybe get_foo
is a member variable.get_foo
是一个成员变量。 Maybe one instance has get_foo = 1
and another instance has get_foo = 5
The computer does not know.get_foo = 1
而另一个实例有get_foo = 5
计算机不知道。 The computer is stupid.get_foo
.get_foo
的成员变量。 It then says, "ah ha! I bet that get_foo
belongs to the CLASS."get_foo
属于 CLASS。” So, it looks there, and lo-and-behold, there it is: Foo
has an attribute named get_foo
.Foo
有一个名为get_foo
的属性。 FooMeta
also has an attribute called get_foo
, but who cares about that. FooMeta
也有一个名为get_foo
的属性,但谁在乎呢。 Something to focus on is that:需要关注的是:
Foo
has an attribute named get_foo
Foo
有一个名为get_foo
的属性MetaFoo
has an attribute named get_foo
MetaFoo
有一个名为get_foo
的属性They both have attributes named get_foo
, but Foo
and MetaFoo
are different objects.它们都有名为
get_foo
的属性,但Foo
和MetaFoo
是不同的对象。 It's not as if the two get_foo
s are shared.两个
get_foo
共享的。 I can have obj1.x = 1
and obj2.x = 99
.我可以有
obj1.x = 1
和obj2.x = 99
。 No problem.没问题。
FooMeta
has its own __getattribute__
method. FooMeta
有自己的__getattribute__
方法。 Before I talked about Foo.__getattribute__
, but now let's talk about the MeTa __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__()
The order of events:事件顺序:
get_foo
s in this problem have a __get__
methodget_foo
都没有__get__
方法Okay now?现在好吗? Why only lines 1 & 2?
为什么只有第 1 行和第 2 行? Because you made a
@classmethod
silly!因为你把
@classmethod
傻了! We check Foo
to see if it has a get_foo
and it does?我们检查
Foo
看它是否有一个get_foo
并且它有? Why check for class attributes if we find an instance attribute first?如果我们首先找到实例属性,为什么还要检查 class 属性? We always check to see if an attribute belong to the instance (
Foo
) first-and-foremost before checking if maybe there happens to be only one copy of a static member variable belonging to the class ( FooMeta
) and shared by all of the instances.我们总是首先检查一个属性是否属于实例(
Foo
) ,然后再检查是否可能恰好只有一个属于FooMeta
并由所有实例共享的 static 成员变量的副本.
Note that if Foo does not have a get_foo
then FooMeta.__getattribute__(Foo, "get_foo")
will return get_foo
from the metaclass because the first attempt (getting it from the instance) failed.请注意,如果 Foo 没有
get_foo
则FooMeta.__getattribute__(Foo, "get_foo")
将从元类返回get_foo
因为第一次尝试(从实例中获取它)失败。 You kinda blocked that option out by giving the instance something of the same name as the class's static member variable.您通过为实例提供与类的 static 成员变量相同的名称来阻止该选项。
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)
Prints:印刷:
14
29
1
does NOT print:不打印:
29
29
29
Note that if you want to set a static class member variable, instance.mem_var = 5
is a very ⱽᵉᴿʸ bad idea.请注意,如果要设置 static class成员变量,
instance.mem_var = 5
是一个非常糟糕的主意。 You will give the instance
a new member variable, and the class static (shared) member variable will be shadowed.您将为
instance
提供一个新的成员变量,并且 class static(共享)成员变量将被隐藏。 You can fix that with something like this:你可以用这样的东西来解决这个问题:
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)
Then your lil' compy will print:然后你的 lil' compy 将打印:
29
29
29
class Foo:
@classmethod
def funky(cls):
pass
is NOT MetaClass.funky = funky
.不是
MetaClass.funky = funky
。 Instead, it's:相反,它是:
def funky(cls)
pass
Funky = classmethod (funky)
... which is almost the same as: ...这几乎与:
def funky(cls):
pass
funky = lambda self, *args, **kwargs: funky(type(self), *args, **kwargs)
The moral of note 4s story is that classmethod
funky
is an attribute of Foo
and not an attribute of FooMeta
note 4s 故事的寓意是
classmethod
funky
是Foo
的属性,而不是FooMeta
的属性
get_foo
is not an attribute of Foo
, but rather an attribute of type(Foo)
: get_foo
不是Foo
的属性,而是type(Foo)
的属性:
>>> 'get_foo' in Foo.__dict__
False
>>> 'get_foo' in type(Foo).__dict__
True
So while Foo.get_foo
will resolve to type(Foo).get_foo
, super().get_foo
does not because the proxy returned by super()
is similar to Foo
, but is not Foo
itself.因此,虽然
Foo.get_foo
将解析为type(Foo).get_foo
,但super().get_foo
不会,因为super()
返回的代理类似于Foo
,但不是Foo
本身。
obj.attr
calls type(obj).__getattribute__(obj, 'attr')
, that is obj.attr
调用type(obj).__getattribute__(obj, 'attr')
,即
object.__getattribute__(obj, 'attr')
, which looks up 'attr'
in obj
itself, as well as in the class of obj
and its parents; object.__getattribute__(obj, 'attr')
,它在obj
本身以及obj
及其父对象的 class 中查找'attr'
; ortype.__getattribute__(obj, 'attr')
if obj
is a type
instance, which looks up 'attr'
in obj
itself and its parents, as well as in the class of obj
and its parents; type.__getattribute__(obj, 'attr')
如果obj
是一个type
实例,它在obj
本身及其父对象中查找'attr'
,以及在obj
及其父对象的 class 中查找 'attr' ; orsuper.__getattribute__(obj, 'attr')
if obj
is a super
instance, super.__getattribute__(obj, 'attr')
如果obj
是一个super
实例,
'attr'
in the class of instance
and its parents past cls
, if obj
is super(cls, instance)
, or instance
的 class 及其父母过去cls
中查找'attr'
,如果obj
是super(cls, instance)
,或者'attr'
in subclass
itself and its parents past cls
, if obj
is super(cls, subclass)
. obj
是super(cls, subclass)
cls
则在subclass
本身及其父类中查找'attr'
cls 。 When you call super().get_foo()
in the class method Foo2.get_foo
, which is equivalent to calling super(Foo2, cls).get_foo()
, you are in case c.2), ie you are looking up 'get_foo'
in cls
itself and its parents past Foo2
, ie you are looking up 'get_foo'
in Foo
.当您在 class 方法
Foo2.get_foo
中调用super().get_foo()
时,相当于调用super(Foo2, cls).get_foo()
时,您是在 c.2) 的情况下,即您正在查找'get_foo'
在cls
本身及其过去Foo2
的父母中,即您在Foo
中查找'get_foo'
。 That is why the call fails.这就是调用失败的原因。
You expect the call super().get_foo()
in the class method Foo2.get_foo
to succeed because you think it is equivalent to the call Foo.get_foo()
which is in case b), ie you think you are looking up 'get_foo'
in cls
itself and its parents, as well as in the class of Foo
( FooMeta
) and its parents .您希望 class 方法
Foo2.get_foo
中的调用super().get_foo()
成功,因为您认为它等同于在案例 b) 中的调用Foo.get_foo()
,即您认为您正在查找'get_foo'
在cls
本身及其父级中,以及在Foo
( FooMeta
) 及其父级的 class 中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.