就算我了解元类...
免责声明:发布之前,我一直在寻找答案,但是发现的大部分答案都涉及到致电super()
来获取MRO中的另一个@classmethod
(不涉及元类),或者令人惊讶的是,其中很多是试图在metaclass.__new__
或metaclass.__call__
中做某事,这意味着该类尚未完全创建。我非常确定(假设是97%),这不是这些问题之一。
环境:Python 3.7.2
问题:
我有一个元类FooMeta
,它定义了一种方法get_foo(cls)
,一个类Foo
是从该元类(因此为FooMeta
的一个实例)构建的,并且具有一个{{1} } @classmethod
。然后是另一个从get_bar(cls)
继承的类Foo2
。在Foo
中,我通过声明Foo2
并调用get_foo
来继承@classmethod
的子类。这真是失败了...
即用此代码
super()
问题:
因此,以我的类为元类的实例,并确认两个类方法都存在于类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'
上,为什么对Foo
的两个调用都不在super().get_***()
内部工作? 我对元类或Foo2
不了解,这使我无法发现这些结果是否合乎逻辑?
编辑:进一步测试表明,super()
上的方法是类方法还是实例方法,不会改变结果。
编辑2:感谢@chepner的回答,我认为问题是Foo2
返回了代表super()
的超级对象(已通过Foo
验证),我期望{ {1}}在幕后表现(甚至可以打电话)super().__thisclass__
。似乎不是……我仍然想知道为什么,但是它越来越清晰了:)
答案 0 :(得分:3)
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的方法解析顺序,即检查属性查找的类的顺序,{ 1}}。
(D, B, C, A, object)
在super().f() + 1
的MRO中搜索cls
的实现。它应该找到的是f
,但是B具有继承的C.f
实现,并且B在MRO中位于C之前。如果f
拿起super
,在通常称为“钻石问题”的情况下,这将中断B.f
覆盖C
的尝试。>
f
并没有直接查看B的属性,而是直接在B的super
中查找,因此它只考虑__dict__
实际提供的属性,而不考虑B
的属性超类或元类。
现在,回到您的B
/ get_foo
情况。 get_bar
来自get_bar
本身,因此Foo
找到super().get_bar()
。但是,Foo.get_bar
不是由get_foo
提供的,而是由Foo
元类提供的,并且FooMeta
中没有get_foo
的条目。因此,Foo.__dict__
一无所获。
答案 1 :(得分:0)
请明确说明:
Foo
不会 从FooMeta
继承。FooMeta
不是 是Foo
super
不起作用。
现在,注释(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()
输出为:
`get_foo` from `FooMeta` was called!
`get_foo` from `FooMeta` was called!
如果您有一个与元类中的方法同名的类方法,为什么要调用元类方法 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
的实例在本文中,我将第一次使用伪代码,而不是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_foo
。”
get_foo
。也许get_foo
是成员变量。也许一个实例有get_foo = 1
,而另一个实例有get_foo = 5
计算机不知道。电脑很笨。get_foo
的成员变量。然后说:“啊哈!我敢打赌get_foo
属于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__()
事件顺序:
get_foo
都没有__get__
方法好吧!为什么只有1和2行?因为您犯了一个@classmethod
很傻!我们检查Foo
来看看它是否有get_foo
!如果先找到实例属性,为什么还要检查类属性?我们总是先检查属性是否属于实例(Foo
) 第一和最重要的 ,然后再检查是否可能只有一个副本属于该类(FooMeta
)并由所有实例共享的静态成员变量。
请注意,如果Foo没有get_foo
,则FooMeta.__getattribute__(Foo, "get_foo")
将从元类返回get_foo
,因为第一次尝试(从实例中获取)失败。您可以通过为实例提供与类的静态成员变量相同的名称来阻止该选项。
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
请注意,如果您要设置静态类成员变量,instance.mem_var = 5
是一个非常不明智的主意。您将为instance
提供一个新的成员变量,并且该类静态(共享)成员变量将被遮盖。您可以使用以下方法解决此问题:
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)
然后您的小动物会打印:
29
29
29
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)
注释4s故事的寓意是classmethod
funky
是Foo
的属性,而不是FooMeta
的属性
答案 2 :(得分:0)
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
,但不是{{ 1}}本身。
答案 3 :(得分:0)
obj.attr
调用 type(obj).__getattribute__(obj, 'attr')
,即
object.__getattribute__(obj, 'attr')
,它在 'attr'
本身以及 obj
及其父类中查找 obj
;或type.__getattribute__(obj, 'attr')
if obj
是一个 type
实例,它在 'attr'
本身和它的父级以及类中查找 obj
obj
及其父母;或super.__getattribute__(obj, 'attr')
如果 obj
是 super
实例,
'attr'
及其父级 instance
之后查找 cls
,如果 obj
是 super(cls, instance)
,或者立>
'attr'
是 subclass
,它会在 cls
本身及其父项中查找 obj
,如果 super(cls, subclass)
是 super().get_foo()
。当你在类方法Foo2.get_foo
中调用super(Foo2, cls).get_foo()
,相当于调用'get_foo'
,你在c.2)的情况下,即你在查找{{1} } 在 cls
本身及其过去 Foo2
的父项中,即您在 'get_foo'
中查找 Foo
。这就是调用失败的原因。
您希望类方法 super().get_foo()
中的调用 Foo2.get_foo
成功,因为您认为它等同于调用 Foo.get_foo()
,即 b) 中的调用,即您认为您正在寻找在 'get_foo'
本身和它的父级,以及在 cls
(Foo
) 和它的父级的类中向上 FooMeta
。