3

最近遇到一个元类调用派生类方法的问题。例如,我得到一个简单的 baseclass testA,它有一个 classmethoddo1(a)

class testA(object):

    @classmethod
    def do1(cls, a):
        print "in testA:",cls, a

然后我构建了一个元类,它实际上只打印 cls:

class testMetaA(type):
    def __init__(cls,cname,bases,cdict):
        print "in testMetaA: %s"%cls

然后我可以使用元类来构建一个子类testB,它可以按预期工作:

class testB(testA):

    @classmethod
    def do1(cls, a):
        print "in testB: %s"%cls
        super(testB, cls).do1(a)
    __metaclass__=testMetaA

它将打印in testMetaA: <class '__main__.testB'>:和testB.do1(a)预期的作品:

>>> testB.do1('hello')
in testB: <class '__main__.testB'>
in testA: <class '__main__.testB'> hello

但是,如果我尝试在包含“ super”的元类中调用类方法,如下所示testMetaB,它将引发错误:NameError: global name 'testC' is not defined

class testMetaB(type):
    def __init__(cls,cname,bases,cdict):
        print "in testMetaB: %s"%cls
        cls.do1("hello")

class testC(testA):

    @classmethod
    def do1(cls, a):
        print "in testC: %s"%cls
        super(testC, cls).do1(a)
    __metaclass__=testMetaB

我终于找到了一种通过使用super(cls, cls)而不是解决它的方法super(testC, cls)

class testD(testA):

    @classmethod
    def do1(cls, a):
        print "in testD: %s"%cls
        super(cls, cls).do1(a)
    __metaclass__=testMetaB

它将打印为:

in testMetaB: <class '__main__.testD'>
in testD: <class '__main__.testD'>
in testA: <class '__main__.testD'> hello

testD.do1(a)也可以按预期工作:

>>> testD.do1('Well done')
in testD: <class '__main__.testD'>
in testA: <class '__main__.testD'> Well done

现在我想知道在 classmethod 中使用 super 的最正确方法是什么?是否应该始终使用super(cls,cls)而不是显式编写当前类名?

谢谢!

@jsbueno

如果某些代码采用动态创建派生类之类的技巧,这很重要 - 如果该名称被分配给另一个对象而不是类本身,则不应使用类名作为 Super 的第一个参数。相反,类方法或self.__class__实例方法的 cls 可以传递给 Super。

这是否意味着通常使用类名来超级是一个坏主意?

对我自己来说,我通常使用super(type(self),self)而不是super(type(self.__class__),self)普通方法。不知道有没有什么大的优势可以使用self.__class__。我像这样重复@jsbueno 示例,这里是 C 使用super(type(self),self). 所以在类被改变D2()时不会改变行为。C

>>> class A(object):
    def do(self):
        print "in class A"


>>> class B(A):
    def do(self):
        super(B, self).do()


>>> class C(A):
    def do(self):
        super(type(self),self).do()

>>> D1=B
>>> D2=C
>>> D1().do()
in class A
>>> D2().do()
in class A
>>> class B(A):
    def do(self):
        print "in new class B"


>>> D1().do()

Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    D1().do()
  File "<pyshell#37>", line 3, in do
    super(B, self).do()
TypeError: super(type, obj): obj must be an instance or subtype of type
>>> class C(A):
    def do(self):
        print "in new class C"
>>> D2().do()
in class A

根据@Don Question的建议,我将python版本放在这里: sys.version= 2.7.2+ (default, Oct 4 2011, 20:06:09) [GCC 4.6.1]

4

1 回答 1

3

但是,如果我尝试调用包含“super”的元类中的类方法,如下 testMetaB,它将引发错误:NameError: global name 'testC' is not defined。

该名称TestC只会在 MetaClass 完成它的工作后绑定到新类 - 也就是从它的__init__(以及之前__init____new__)方法返回之后。

当我们使用“super”调用使用类名作为第一个参数时,类名不会神奇地出现在那里:它是一个(模块)全局变量,类本身被分配给它 - 在正常情况下。

在这种情况下,尚未分配名称 - 但是,因为它是一个类方法,所以你在cls变量中有对类的引用 - 这就是它起作用的原因。

如果某些代码采用动态创建派生类之类的技巧,这很重要 - 如果该名称被分配给另一个对象而不是类本身,则不应使用类名作为 Super 的第一个参数。相反,cls对于类方法,或者self.__class__例如方法可以传递给 Super。

这是一个片段,显示类名的全局名称绑定是 super 需要的:

>>> class A(object):
...   def do(self):
...      print "In class A"
... 
>>> class B(A):
...   def do(self):
...     super(B, self).do()
... 
>>> C = B
>>> C().do()
In class A
>>> class B(object):
...   def do(self):
...      print "in new class B"
... 
>>> C().do()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in do
TypeError: super(type, obj): obj must be an instance or subtype of type
>>> 
于 2012-07-13T11:47:31.963 回答