如何检索元类方法

时间:2016-05-10 03:39:49

标签: python metaclass

在Python中工作,如何通过实例化的类检索元类(metamethod)所拥有的方法?在以下场景中,它很简单 - 只需使用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)。由于这个事实,重写这样的方法动态变得困难,因为元类不在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'"

那么,正确覆盖元方法的最简单方法是什么?我已经回答了这个问题,但期待任何建议或替代答案。提前感谢您的回复。

4 个答案:

答案 0 :(得分:5)

正如你所说,并且在实践中发现,A.method在B&#39> s查找链上 - 类和元类的关系不是继承的关系 - 它是其中一个'实例'作为类是元类的实例。

Python是一种优秀的语言,它以预期的方式运行,很少有惊喜 - 在这种情况下也是如此:如果我们正在处理普通的'对象,您的情况与类B实例 A相同。 B.method中会出现B.__dict__ - "超级"}对这样一个实例定义的方法的调用永远不会到达A.method - 它实际上会产生错误。由于B是一个类对象,因此{B} super中方法中的__dict__有意义 - 但它会搜索B' __mro__链类(在这种情况下,(object,)) - 这就是你所看到的。

这种情况不应该经常发生,我甚至认为它根本不会发生;语义上很难在一个方法中存在任何意义,它既可以作为元类方法,也可以作为类本身的方法。此外,如果在method注释中未重新定义B,则它甚至无法从B&B实例中看到(也无法调用)。

也许你的设计应该:

一个。有一个基类Base,使用您的元类A定义method而不是在A中定义 - 然后定义class B(Base):而不是

湾或者让元类A在其创建的每个method中注入class,并在其__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)

这将是我的首选方法 - 但它不允许 一个在使用此元类的类中重写此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,就像它在B案例中工作一样只是A的一些实例,就像我在第二段:B.__class__ is A

在这两种情况下,您都可以使用简单method

来防止在元类中调用不存在的if hasattr(cls.__class__, "method"): ...

答案 1 :(得分:1)

覆盖Sub Compare() Dim WS As Worksheet Dim lo As ListObject Dim Xaxis As Range Set WS = Worksheets("SQL Table") 'sheet that has SQL generated table Set lo = WS.ListObjects("Table") 'Table is the name of the table Dim A As String A = WS.Range("A1").Value 'A1 in this case has the value A in it Set Xaxis = lo.ListColumns(A).DataBodyRange End Sub 不是问题;正确使用method是。 super不是从任何基类继承的,并且您不能保证MRO中的每个类都使用相同的元类来提供该方法,因此您遇到与没有根类来处理的任何方法相同的问题它没有调用method

仅仅因为元类可以注入类方法并不意味着你可以使用该方法进行协作继承。

答案 2 :(得分:0)

这是我第一次尝试解决方案:

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'"

答案 3 :(得分:0)

我认为这最能解决检索元方法的初始问题。

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)

但是,这并没有真正解决在将类完全实例化为@jsbueno explained以响应我的特定用例之前正确覆盖一个方法的问题。