【发布时间】:2012-07-13 05:36:37
【问题描述】:
最近,我遇到了元类调用派生类方法的问题。
比如我得到一个简单的基类testA,它有一个类方法do1(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
现在我想知道在类方法中使用 super 的最正确方法是什么?是否应该始终使用super(cls,cls) 而不是显式编写当前类名?
谢谢!
@jsbueno
如果某些代码采用动态创建派生类之类的技巧,这很重要 - 如果该名称分配给另一个对象而不是类本身,则不应将类名称用作 Super 的第一个参数。相反,可以将类方法的 cls 或实例方法的
self.__class__传递给 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]
【问题讨论】:
-
@Don 问题:“您的元类 testMetaA 实际上没有构建类。”它会的,您实际上可以尝试将函数包装在
__init__中。不覆盖元类__new__并不意味着它不会使用超元类的__new__。你可以在网上找到很多例子。您应该注意到,与普通类一样,元类的__new__的第一个参数与元类的__init__的第一个参数完全不同。 -
除了要修改类名或基元组外,可以一直坚持
__init__。我觉得比__new__方便,因为在__init__之后__new__里面,元类的实例cls其实是存在的。当然,在__new__中,也可以先使用cls=super(testMetaB,meta).__new__(meta,cname,bases,cdict)构建cls。然后在__new__里面修改。 -
对我来说,
self实际上是指普通类的一个实例。我通常使用meta作为元类__new__的第一个参数,cls指的是元类的实例(这是一个普通类)。你可以使用任何你想要的词,没关系。无论如何,这与我的问题完全无关。 -
@Don 问题:“啊,你把你的元类放在方法定义下”;这实际上无关紧要,您可以将位置更改为类 def 的第一行。它不会影响任何事情。
-
Don 问题:抱歉,您的 cmets 关于元类的工作方式是错误的。我觉得他们在这里给上下文增加了混乱。说的很清楚: (1) 元类不需要指定
__new__方法。如果不存在,则使用默认的type的__new__。 (2) 在类主体上分配__metaclass__的位置是没有意义的——只有在解析类主体并且在其上下文中分配的所有名称都在普通字典中时才考虑它。 (Python 3 的行为不同)
标签: python superclass metaclass class-method