在元类中使用 __call__ 时 __init__ 错误的回溯行为?
traceback behaviour for __init__ errors when using __call__ in metaclasses?
使用以下代码:
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
是否也会从其 __call__
触发 class 的 __init__
,或者当我添加元 class 时行为是否发生了变化?
-
__new__
和 __call__
都被 运行 调用 class 构造函数。为什么 __call__
出现在错误消息中而不是 __new__
?
- 有没有办法在此处抑制显示元 class 的
__call__
的回溯行?即,当错误出现在对构造函数的调用中而不是 __call__
代码中时?
事实上,解释器抱怨的是你没有将 arg 传递给 __init__
。
你应该这样做:
t = TestClass('arg')
或:
class TestClass(object):
__metaclass__ = Meta
def __init__(self):
pass
t = TestClass()
看看我能不能回答你的三个问题:
Does type
also trigger the __init__
of the class from its __call__
or is the behaviour changed when I add the metaclass?
type.__call__
的默认行为是用cls.__new__
创建一个新对象(可能继承自object.__new__
,或者用super
调用它)。如果从 cls.__new__
编辑的对象 return 是 cls
的一个实例,type.__call__
将在其上 运行 cls.__init__
。
如果您在自定义元class 中定义自己的__call__
方法,它几乎可以做任何事情。通常虽然你会在某个时候调用 type.__call__
(通过 super
),但同样的行为也会发生。但这不是必需的。您可以 return 元 class 的 __call__
方法中的任何内容。
Both __new__
and __call__
are being run by the call to the class constructor. Why is __call__
showing up in the error message but not __new__
?
您误解了 Meta.__new__
的用途。当您创建普通 class 的实例时,不会调用 metaclass 中的 __new__
方法。当您创建 metaclass 的实例时调用它,它是 class 对象本身。
尝试 运行ning 这段代码,以更好地理解发生了什么:
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__
是在定义常规 class Cls
的地方调用的,而不是在创建 Cls
的实例时调用的。 Cls
class 对象实际上是 Meta
的一个实例,所以这是有道理的。
您的异常回溯的差异来自于这个事实。当异常发生时,metaclass 的 __new__
方法早就结束了(因为如果它没有发生,就根本不会有常规的 class 可以调用) .
Is there a way of suppressing the lines of the traceback showing the __call__
for the metaclass here? i.e. when the error is in the call to the constructor and not the __call__
code?
是也不是。这可能是可能的,但它几乎肯定是一个坏主意。默认情况下,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
时参数数量错误才会引发异常。
我想如果你需要的话,你可以通过在某个地方编辑堆栈跟踪对象中的数据来使事情变得更漂亮,但如果你自动这样做,如果你遇到实际的情况,可能会使事情变得更加混乱元class 代码错误。
使用以下代码:
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
是否也会从其__call__
触发 class 的__init__
,或者当我添加元 class 时行为是否发生了变化?-
__new__
和__call__
都被 运行 调用 class 构造函数。为什么__call__
出现在错误消息中而不是__new__
? - 有没有办法在此处抑制显示元 class 的
__call__
的回溯行?即,当错误出现在对构造函数的调用中而不是__call__
代码中时?
事实上,解释器抱怨的是你没有将 arg 传递给 __init__
。
你应该这样做:
t = TestClass('arg')
或:
class TestClass(object):
__metaclass__ = Meta
def __init__(self):
pass
t = TestClass()
看看我能不能回答你的三个问题:
Does
type
also trigger the__init__
of the class from its__call__
or is the behaviour changed when I add the metaclass?
type.__call__
的默认行为是用cls.__new__
创建一个新对象(可能继承自object.__new__
,或者用super
调用它)。如果从 cls.__new__
编辑的对象 return 是 cls
的一个实例,type.__call__
将在其上 运行 cls.__init__
。
如果您在自定义元class 中定义自己的__call__
方法,它几乎可以做任何事情。通常虽然你会在某个时候调用 type.__call__
(通过 super
),但同样的行为也会发生。但这不是必需的。您可以 return 元 class 的 __call__
方法中的任何内容。
Both
__new__
and__call__
are being run by the call to the class constructor. Why is__call__
showing up in the error message but not__new__
?
您误解了 Meta.__new__
的用途。当您创建普通 class 的实例时,不会调用 metaclass 中的 __new__
方法。当您创建 metaclass 的实例时调用它,它是 class 对象本身。
尝试 运行ning 这段代码,以更好地理解发生了什么:
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__
是在定义常规 class Cls
的地方调用的,而不是在创建 Cls
的实例时调用的。 Cls
class 对象实际上是 Meta
的一个实例,所以这是有道理的。
您的异常回溯的差异来自于这个事实。当异常发生时,metaclass 的 __new__
方法早就结束了(因为如果它没有发生,就根本不会有常规的 class 可以调用) .
Is there a way of suppressing the lines of the traceback showing the
__call__
for the metaclass here? i.e. when the error is in the call to the constructor and not the__call__
code?
是也不是。这可能是可能的,但它几乎肯定是一个坏主意。默认情况下,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
时参数数量错误才会引发异常。
我想如果你需要的话,你可以通过在某个地方编辑堆栈跟踪对象中的数据来使事情变得更漂亮,但如果你自动这样做,如果你遇到实际的情况,可能会使事情变得更加混乱元class 代码错误。