在Python中工作,如何通过它实例化的类检索元类(元方法)所拥有的方法?在下面的场景中,这很简单--只需使用getattr或点符号:
*所有示例都使用版本安全的http://pythonhosted.org/six/#six.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)中的任何地方都找不到。由于这一事实,动态重写这种方法变得困难,因为元类不在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'"那么,正确覆盖元方法的最简单方法是什么?我已经对这个问题提出了我自己的答案,但期待着任何建议或替代答案。提前感谢您的回复。
发布于 2016-05-10 04:17:44
正如您所说的,并在实践中发现,A.method不在B的查找链上-类和元类的关系不是继承关系-它是“实例”之一,因为类是元类的一个实例。
Python是一种很好的语言,它以预期的方式运行,很少有惊喜--在这种情况下也是如此:如果我们处理的是“普通”对象,您的情况将与拥有类B的实例A相同。B.method将出现在B.__dict__中--在为这样一个实例定义的方法上放置的“超级”调用永远不会到达A.method --它实际上会产生错误。因为B是一个类对象,所以B的__dict__中的方法中的super是有意义的--但是它将搜索B的__mro__类链(在本例中是(object,)) --这就是您所点击的。
这种情况不应该经常发生,我甚至认为根本不应该发生这种情况;在语义上,在作为元类方法和类本身的方法都有意义的方法中很难存在任何意义。此外,如果method没有在B中重新定义,那么它甚至不能从B的实例中看到(也不能调用)。
也许你的设计应该:
拥有一个基本的Base,使用元类A定义method,而不是在A中定义它,然后定义class B(Base):
b.或者让元类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):
setattr(cls, 'method', classmethod(method))这将是我最喜欢的方法--但它不允许在使用此元类的类中覆盖这个method --如果没有一些额外的逻辑,上面的方法将更倾向于覆盖主体中的任何这样的method。更简单的方法是像上面那样有一个基类Base,或者注入一个名称不同的方法,比如最后一个类上的base_method,然后在任何重写的method中对它进行硬编码调用。
class B(metaclass=A):
@classmethod
def method(cls):
cls.base_method()
...(在元类的if上使用额外的__init__,以便将默认的method别名为base_method )
你真正想要的东西从这里开始,
现在,如果您确实有一个从类调用元类中的方法的用例,那么“唯一和明显的”方法就是简单地硬编码调用,就像在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,就像在cls中一样,B只是一些A的实例,就像我在第二段中的例子:B.__class__ is A)
在这两种情况下,您都可以避免使用简单的method调用元类中不存在的if hasattr(cls.__class__, "method"): ...。
发布于 2016-05-10 04:06:23
重写method不是问题;正确使用super是问题。method不是从任何基类继承的,而且不能保证MRO中的每个类都使用相同的元类来提供方法,因此您遇到的问题与没有根类的任何方法在不调用super的情况下都会遇到相同的问题。
仅仅因为元类可以注入类方法,并不意味着您可以将该方法用于协作继承。
发布于 2016-05-10 06:22:27
这是我第一次尝试一个解决方案:
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'"https://stackoverflow.com/questions/37128880
复制相似问题