如何检索元类方法
How to Retrieve a Metaclass Method
在 Python 中工作,如何通过实例化的 class 检索元 class(元方法)拥有的方法?在下面的场景中,这很简单——只需使用 getattr
或点符号:
* 所有示例都使用版本安全 with_metaclass
class A(type):
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(with_metaclass(A, object)):
"""a class"""
pass
B.method()
# prints: "this is a metamethod of 'B'"
但这很奇怪,因为 'method'
在 dir(B)
的任何地方都找不到。由于这个事实,动态覆盖这样的方法变得困难,因为 metaclass 不在 super
:
的查找链中
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(with_metaclass(A, object)):
"""a class"""
@classmethod
def method(cls):
super(B, cls).method()
B.method()
# raises: "AttributeError: 'super' object has no attribute 'method'"
那么,正确覆盖元方法的最简单方法是什么?我已经对这个问题提出了自己的答案,但期待任何建议或替代答案。预先感谢您的回复。
覆盖method
不是问题;正确使用 super
是。 method
没有继承自任何基础 class,你不能保证 MRO 中的每个 class 使用相同的元 class 来提供方法,所以你 运行 进入与任何没有根 class 的方法相同的问题来处理它而不调用 super
.
仅仅因为 metaclass 可以注入 class 方法并不意味着您可以使用该方法进行协作继承。
正如你所说,在实践中发现,A.method
在 B 的查找链上是 而不是 - classes 和 meta[= 的关系93=]es 不是继承之一 - 它是 'instances' 之一,因为 class 是 metaclass.
的一个实例
Python 是一种很好的语言,它以预期的方式运行,很少有意外——在这种情况下也是一样的:如果我们正在处理 'ordinary' 对象,你的情况与拥有 class A
的 实例 B
相同。并且 B.method
将出现在 B.__dict__
中 - 对为此类实例定义的方法进行的 "super" 调用永远无法到达 A.method
- 它实际上会产生错误。由于 B
是一个 class 对象,B 的 __dict__
中的方法内部的 super
是有意义的 - 但它将在 B 的 __mro__
链上搜索 classes(在这种情况下,(object,)
)——这就是你命中的。
这种情况不应该经常发生,我什至认为根本不应该发生;从语义上讲,很难在既作为元class 方法又作为class 本身的方法都有意义的方法中存在任何意义。此外,如果 method
未在 B
中重新定义,请注意它甚至不会从 B 的实例中可见(也不可调用)。
也许您的设计应该:
一个。有一个 baseclass Base
,使用你的 metaclass A
,定义 method
而不是在 A
中定义 - 然后定义class B(Base):
改为
b。或者让 metaclass A
在它创建的每个 class
中注入 method
,在它的 __init__
或 __new__
方法中使用代码 -沿着:
def method(cls):
m = "this is an injected method of '%s'"
print(m % cls.__name__)
class A(type):
def __init__(cls, name, bases, dct):
dct['method'] = classmethod(method)
这是我的首选方法 - 但它不允许
一个在使用此 metaclass 的 class 中覆盖此 method
-
如果没有一些额外的逻辑,上面的方法宁愿覆盖正文中任何这样的 method
显式。
更简单的方法是像上面那样有一个基础 class Base
,或者注入一个具有不同名称的方法,比如在最后的 class 上注入 base_method
,然后硬编码调用它在任何覆盖 method
:
class B(metaclass=A):
@classmethod
def method(cls):
cls.base_method()
...
(在 metaclass 的 __init__
上使用额外的 if
以便默认 method
别名为 base_method
)
您真正要求的内容从这里开始
现在,如果您确实有从 class 调用元 class 中的方法的用例,"one and obvious" 方法就是简单地硬编码调用,如它是在 super
存在之前完成的
您可以执行以下任一操作:
class B(metaclass=A):
@classmethod
def method(cls):
A.method(cls)
...
哪个不那么动态,但不那么神奇,更易读——或者:
class B(metaclass=A):
@classmethod
def method(cls):
cls.__class__.method(cls)
...
哪个更动态(__class__
属性适用于 cls
就像它在 B
只是 A
的某个实例的情况下一样适用,就像我的例子在第二段中:B.__class__ is A
)
在这两种情况下,您都可以避免使用简单的 if hasattr(cls.__class__, "method"): ...
调用 metaclass 中不存在的 method
这是我第一次尝试解决方案:
def metamethod(cls, name):
"""Get a method owned by a metaclass
The metamethod is retrieved from the first metaclass that is found
to have instantiated the given class, or one of its parent classes
provided the method doesn't exist in the instantiated lookup chain.
Parameters
----------
cls: class
The class whose mro will be searched for the metamethod
"""
for c in cls.mro()[1:]:
if name not in c.__dict__ and hasattr(c, name):
found = getattr(c, name)
if isinstance(found, types.MethodType):
new = classmethod(found.__func__)
return new.__get__(None, cls)
else:
m = "No metaclass attribute '%s' found"
raise AttributeError(m % name)
然而,这有一个问题——它不适用于 six.with_metaclass
。解决方法是重新创建它,并使用它在继承自它的 类 的 mro 中创建一个新基础。因为这个基础不是临时的,所以实现其实很简单:
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("BaseWithMeta", bases, {})
瞧瞧,这种方法确实解决了我提出的案例:
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(six.with_metaclass(A, object)):
"""a class"""
@classmethod
def method(cls):
metamethod(cls, 'method')()
B.method()
# prints: "this is a metamethod of 'B'"
但是,它会失败,并且会以同样的方式失败,并且出于同样的原因:
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class C(object):
"""a class"""
@classmethod
def method(cls):
m = "this is a classmethod of '%s'"
print(m % cls.__name__)
class B(six.with_metaclass(A, C)):
"""a class"""
pass
B.method()
# prints: "this is a classmethod of 'B'"
我认为这最接近于解决检索元方法的初始问题。
def metamethod(cls, name):
"""Get an unbound method owned by a metaclass
The method is retrieved from the instantiating metaclass.
Parameters
----------
cls: class
The class whose instantiating metaclass will be searched.
"""
if hasattr(cls.__class__, name):
return getattr(cls.__class__, name)
但是,这并没有真正解决正确覆盖在 class 完全实例化为 @jsbueno 之前调用的方法的问题,以响应我的特定用例。
在 Python 中工作,如何通过实例化的 class 检索元 class(元方法)拥有的方法?在下面的场景中,这很简单——只需使用 getattr
或点符号:
* 所有示例都使用版本安全 with_metaclass
class A(type):
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(with_metaclass(A, object)):
"""a class"""
pass
B.method()
# prints: "this is a metamethod of 'B'"
但这很奇怪,因为 'method'
在 dir(B)
的任何地方都找不到。由于这个事实,动态覆盖这样的方法变得困难,因为 metaclass 不在 super
:
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(with_metaclass(A, object)):
"""a class"""
@classmethod
def method(cls):
super(B, cls).method()
B.method()
# raises: "AttributeError: 'super' object has no attribute 'method'"
那么,正确覆盖元方法的最简单方法是什么?我已经对这个问题提出了自己的答案,但期待任何建议或替代答案。预先感谢您的回复。
覆盖method
不是问题;正确使用 super
是。 method
没有继承自任何基础 class,你不能保证 MRO 中的每个 class 使用相同的元 class 来提供方法,所以你 运行 进入与任何没有根 class 的方法相同的问题来处理它而不调用 super
.
仅仅因为 metaclass 可以注入 class 方法并不意味着您可以使用该方法进行协作继承。
正如你所说,在实践中发现,A.method
在 B 的查找链上是 而不是 - classes 和 meta[= 的关系93=]es 不是继承之一 - 它是 'instances' 之一,因为 class 是 metaclass.
Python 是一种很好的语言,它以预期的方式运行,很少有意外——在这种情况下也是一样的:如果我们正在处理 'ordinary' 对象,你的情况与拥有 class A
的 实例 B
相同。并且 B.method
将出现在 B.__dict__
中 - 对为此类实例定义的方法进行的 "super" 调用永远无法到达 A.method
- 它实际上会产生错误。由于 B
是一个 class 对象,B 的 __dict__
中的方法内部的 super
是有意义的 - 但它将在 B 的 __mro__
链上搜索 classes(在这种情况下,(object,)
)——这就是你命中的。
这种情况不应该经常发生,我什至认为根本不应该发生;从语义上讲,很难在既作为元class 方法又作为class 本身的方法都有意义的方法中存在任何意义。此外,如果 method
未在 B
中重新定义,请注意它甚至不会从 B 的实例中可见(也不可调用)。
也许您的设计应该:
一个。有一个 baseclass Base
,使用你的 metaclass A
,定义 method
而不是在 A
中定义 - 然后定义class B(Base):
改为
b。或者让 metaclass A
在它创建的每个 class
中注入 method
,在它的 __init__
或 __new__
方法中使用代码 -沿着:
def method(cls):
m = "this is an injected method of '%s'"
print(m % cls.__name__)
class A(type):
def __init__(cls, name, bases, dct):
dct['method'] = classmethod(method)
这是我的首选方法 - 但它不允许
一个在使用此 metaclass 的 class 中覆盖此 method
-
如果没有一些额外的逻辑,上面的方法宁愿覆盖正文中任何这样的 method
显式。
更简单的方法是像上面那样有一个基础 class Base
,或者注入一个具有不同名称的方法,比如在最后的 class 上注入 base_method
,然后硬编码调用它在任何覆盖 method
:
class B(metaclass=A):
@classmethod
def method(cls):
cls.base_method()
...
(在 metaclass 的 __init__
上使用额外的 if
以便默认 method
别名为 base_method
)
您真正要求的内容从这里开始
现在,如果您确实有从 class 调用元 class 中的方法的用例,"one and obvious" 方法就是简单地硬编码调用,如它是在 super
您可以执行以下任一操作:
class B(metaclass=A):
@classmethod
def method(cls):
A.method(cls)
...
哪个不那么动态,但不那么神奇,更易读——或者:
class B(metaclass=A):
@classmethod
def method(cls):
cls.__class__.method(cls)
...
哪个更动态(__class__
属性适用于 cls
就像它在 B
只是 A
的某个实例的情况下一样适用,就像我的例子在第二段中:B.__class__ is A
)
在这两种情况下,您都可以避免使用简单的 if hasattr(cls.__class__, "method"): ...
method
这是我第一次尝试解决方案:
def metamethod(cls, name):
"""Get a method owned by a metaclass
The metamethod is retrieved from the first metaclass that is found
to have instantiated the given class, or one of its parent classes
provided the method doesn't exist in the instantiated lookup chain.
Parameters
----------
cls: class
The class whose mro will be searched for the metamethod
"""
for c in cls.mro()[1:]:
if name not in c.__dict__ and hasattr(c, name):
found = getattr(c, name)
if isinstance(found, types.MethodType):
new = classmethod(found.__func__)
return new.__get__(None, cls)
else:
m = "No metaclass attribute '%s' found"
raise AttributeError(m % name)
然而,这有一个问题——它不适用于 six.with_metaclass
。解决方法是重新创建它,并使用它在继承自它的 类 的 mro 中创建一个新基础。因为这个基础不是临时的,所以实现其实很简单:
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("BaseWithMeta", bases, {})
瞧瞧,这种方法确实解决了我提出的案例:
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class B(six.with_metaclass(A, object)):
"""a class"""
@classmethod
def method(cls):
metamethod(cls, 'method')()
B.method()
# prints: "this is a metamethod of 'B'"
但是,它会失败,并且会以同样的方式失败,并且出于同样的原因:
class A(type):
"""a metaclass"""
def method(cls):
m = "this is a metamethod of '%s'"
print(m % cls.__name__)
class C(object):
"""a class"""
@classmethod
def method(cls):
m = "this is a classmethod of '%s'"
print(m % cls.__name__)
class B(six.with_metaclass(A, C)):
"""a class"""
pass
B.method()
# prints: "this is a classmethod of 'B'"
我认为这最接近于解决检索元方法的初始问题。
def metamethod(cls, name):
"""Get an unbound method owned by a metaclass
The method is retrieved from the instantiating metaclass.
Parameters
----------
cls: class
The class whose instantiating metaclass will be searched.
"""
if hasattr(cls.__class__, name):
return getattr(cls.__class__, name)
但是,这并没有真正解决正确覆盖在 class 完全实例化为 @jsbueno