Let's say I have some class that calls __new__
, how do I play well with the mro and call up to super classes' __new__
(with arguments) as necessary, but not call object.__new__
with additional arguments? E.g. this only works if you pass no arguments to the constructor:
class A(object):
def __new__(cls, *args, **kwargs):
print("A.__new__ called")
return super(A, cls).__new__(cls, *args, **kwargs)
class B(object):
def __new__(cls, *args, **kwargs):
print("B.__new__ called")
return super(B, cls).__new__(cls, *args, **kwargs)
class C(A, B): pass
>>> C.mro()
[__main__.C, __main__.A, __main__.B, builtins.object]
>>> C()
A.__new__ called
B.__new__ called
But calling with arguments causes it to fail (because at some point Python changed to have object.__new__
accept no parameters except for caller class):
>>> C(1)
A.__new__ called
B.__new__ called
Traceback (most recent call last):
...
TypeError: object() takes no parameters
So how can A
or B
tell that their super class is object
? Should I do something like super(A, cls).__new__ is object.__new__
? Check that mro()[1] == builtins.object
? Or do I just need to decide "A will never try to call super classes nicely in __new__
" (e.g. by doing return object.__new__(cls)
)?
EDIT: if the class defines a custom __init__
method, Python 2.6 and Python 2.7 are fine with it, but it will still not work in Python 3.
If we look at the CPython source, we see that:
object.__new__
will error if called with extra arguments, and either __new__
is overridden or __init__
is not overridden; likewise,object.__init__
will error if called with extra arguments, and either __init__
is overridden or __new__
is not overriddenIn 2.x the error was just a DeprecationWarning
, which it's possible you may have missed when experimenting (note that since 2.7 and 3.2 DeprecationWarning
is suppressed by default).
The justification for this behaviour is that types are either immutable - in which case they should consume arguments in __new__
- or mutable - in which case they should consume arguments in __init__
. This appears to be a case of practicality beats purity; it's difficult to think of many productive uses of __new__
delegating to the supertype (as opposed to just logging/tracing), and it's going to be a lot more common that a super
call to object.__new__
with extra arguments is a mistake.
The cleanest way to handle this is going to be to check identity: super(A, cls).__new__ is object.__new__
. Justifications:
this is how CPython decides whether to error (in C rather than Python, but the logic is the same):
if (excess_args(args, kwds) &&
(type->tp_init == object_init || type->tp_new != object_new)) {
PyErr_SetString(PyExc_TypeError, "object() takes no parameters");
return NULL;
}
object.__new__
does nothing with the arguments, except to decide whether to error out.
It is really up to the custom __new__
method to not pass on arguments it itself used and needed. If neither C
, B
or A
need arguments, then the fact that object.__new__()
raises an exception is entirely correct.
Note that as soon as you have a custom __init__
as well, then object.__new__
won't raise the exception either. That's because now there is somewhere to hand those remaining arguments to:
>>> class A(object):
... def __new__(cls, *args, **kwargs):
... print("A.__new__ called")
... return super(A, cls).__new__(cls, *args, **kwargs)
...
>>> class B(object):
... def __new__(cls, *args, **kwargs):
... print("B.__new__ called")
... return super(B, cls).__new__(cls, *args, **kwargs)
...
>>> class C(A, B):
... def __init__(self, *args, **kwargs):
... print args, kwargs
...
>>> C(1)
A.__new__ called
B.__new__ called
(1,) {}
<__main__.C object at 0x10e7f9790>
It does appear that this behaviour changed in Python 3.
You can test to see what __new__
method is returned:
class B(object):
def __new__(cls, *args, **kwargs):
new = super(B, cls).__new__
if new is object.__new__:
return new(cls)
return new(cls, *args, **kwargs)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With