使用以下代码:
class Meta(type):
def __new__(mcl, name, bases, nmspc):
return super(Meta, mcl).__new__(mcl, name, bases, nmspc)
class TestClass(object):
__metaclass__ = Meta
def __init__(self):
pass
t = TestClass(2) # deliberate error
产生以下内容:
Traceback (most recent call last):
File "foo.py", line 12, in <module>
t = TestClass(2)
TypeError: __init__() takes exactly 1 argument (2 given)
但是,在以下代码中使用__call__
代替__new__
:
class Meta(type):
def __call__(cls, *args, **kwargs):
instance = super(Meta, cls).__call__(*args, **kwargs)
# do something
return instance
class TestClass(object):
__metaclass__ = Meta
def __init__(self):
pass
t = TestClass(2) # deliberate error
给我以下追溯:
Traceback (most recent call last):
File "foo.py", line 14, in <module>
t = TestClass(2)
File "foo.py", line 4, in __call__
instance = super(Meta, cls).__call__(*args, **kwargs)
TypeError: __init__() takes exactly 1 argument (2 given)
type
是否还会从__init__
触发类的__call__
,还是在添加元类时更改了行为? __new__
和__call__
都是通过调用类构造函数运行的。为什么__call__
会显示在错误消息中而不是__new__
?__call__
?即错误是在构造函数的调用而不是__call__
代码? 答案 0 :(得分:3)
让我们看看我能回答你的三个问题:
type
是否还会从__init__
触发类的__call__
,或者在添加元类时是否更改了行为?
type.__call__
的默认行为是使用cls.__new__
创建一个新对象(可以从object.__new__
继承,或者使用super
调用它)。如果从cls.__new__
返回的对象是cls
的实例,则type.__call__
将在其上运行cls.__init__
。
如果您在自定义元类中定义自己的__call__
方法,它几乎可以执行任何操作。通常,您会在某个时候(通过type.__call__
)调用super
,因此会发生相同的行为。但这并不是必需的。您可以从元类的__call__
方法返回任何内容。
__new__
和__call__
都是通过调用类构造函数来运行的。为什么__call__
会显示在错误消息中而不是__new__
?
您误解了Meta.__new__
的用途。在创建普通类的实例时,不会调用元类中的__new__
方法。在创建元类的实例时调用它,它是类对象本身。
尝试运行此代码,以便更好地了解正在发生的事情:
print("Before Meta")
class Meta(type):
def __new__(meta, name, bases, dct):
print("In Meta.__new__")
return super(Meta, meta).__new__(meta, name, bases, dct)
def __call__(cls, *args, **kwargs):
print("In Meta.__call__")
return super(Meta, cls).__call__(*args, **kwargs)
print("After Meta, before Cls")
class Cls(object):
__metaclass__ = Meta
def __init__(self):
print("In Cls.__init__")
print("After Cls, before obj")
obj = Cls()
print("Bottom of file")
您获得的输出是:
Before Meta
After Meta, before Cls
In Meta.__new__
After Cls, before obj
In Meta.__call__
In Cls.__init__
Bottom of file
请注意,Meta.__new__
在定义常规类Cls
时调用,而不是在创建Cls
实例时调用。 Cls
类对象实际上是Meta
的一个实例,所以这是有道理的。
异常回溯的不同来自于这一事实。当异常发生时,元类的__new__
方法早已完成(因为如果它没有,那么根本就没有一个常规类可以调用)。
有没有办法抑制回溯线显示元类的
__call__
?即错误是在构造函数的调用中而不是__call__
代码?
是和否。它可能是可能的,但它几乎肯定是一个坏主意。默认情况下,Python的堆栈跟踪将显示完整的调用堆栈(不包括在C中实现的内置函数,而不是Python)。这就是他们的目的。在代码中导致异常的问题并不总是在最后一次调用中,即使在比metaclasses更不容易混淆的区域。
考虑这个简单的例子:
def a(*args):
b(args) # note, no * here, so a single tuple will be passed on
def b(*args):
c(*args):
def c():
print(x)
a()
在此代码中,a
函数中存在错误,但仅当b
使用错误的参数数调用c
时才会引发异常。
我想如果你需要通过在某处编辑堆栈跟踪对象中的数据来稍微改善一些事情,但如果你自动这样做,如果你遇到实际的错误,可能会让事情变得更加混乱。元类代码。
答案 1 :(得分:-1)
事实上,翻译所抱怨的是你没有将arg传递给__init__
。
你应该这样做:
t = TestClass('arg')
或:
class TestClass(object):
__metaclass__ = Meta
def __init__(self):
pass
t = TestClass()