Reputation: 37461
I'm converting some code from python2 to python3 and I'm hitting an error with a metaclass.
This is the working python2 code (simplified):
#!/usr/bin/env python2
# test2.py
class Meta(type):
def __new__(mcs, name, bases, clsdict):
new_class = type.__new__(mcs, name, bases, clsdict)
return new_class
class Root(object):
__metaclass__ = Meta
def __init__(self, value=None):
self.value = value
super(Root, self).__init__()
class Sub(Root):
def __init__(self, value=None):
super(Sub, self).__init__(value=value)
def __new__(cls, value=None):
super(Sub, cls).__new__(cls, value)
if __name__ == '__main__':
sub = Sub(1)
And here's the converted python3 code:
#!/usr/bin/env python3
# test3.py
class Meta(type):
def __new__(mcs, name, bases, clsdict):
new_class = type.__new__(mcs, name, bases, clsdict)
return new_class
class Root(object, metaclass=Meta):
def __init__(self, value=None):
self.value = value
super(Root, self).__init__()
class Sub(Root):
def __init__(self, value=None):
super(Sub, self).__init__(value=value)
def __new__(cls, value=None):
super(Sub, cls).__new__(cls, value)
if __name__ == '__main__':
sub = Sub(1)
If I run python2 test2.py
, it runs. If I do python3 test3.py
, I get
Traceback (most recent call last):
File "test.py", line 21, in <module>
sub = Sub(1)
File "test.py", line 18, in __new__
super(Sub, cls).__new__(cls, value)
TypeError: object() takes no parameters
This isn't a duplicate of the linked question because in that one the asker wasn't invoking a simple class correctly. In this one I have code which worked in python 2 and doesn't work with 2to3 run on it
Upvotes: 4
Views: 1496
Reputation: 104852
As described in depth by a comment in the Python 2 source code (as linked by user2357112 in a comment), Python considers it an error if you pass arguments to either object.__new__
or object.__init__
when both __init__
and __new__
have been overridden. If you override just one of those functions, the other one will ignore excess arguments, but if you override them both you're supposed to make sure you only pass on arguments that are appropriate.
In this case, your Root
class overrides __init__
but not __new__
, so the extra argument that gets passed to the inherited object.__new__
when you create an instance are ignored.
However, in Sub
, you're overriding both functions, and Sub.__new__
passes the parameter value
on to object.__new__
in its super
call. This is where you get an exception.
It's technically an error in Python 2 as well as Python 3, but the Python developers decided that raising an exception in that situation would cause too much old code to break, so Python 2 only issues a warning (which is suppressed by default). Python 3 breaks backwards compatibility in several other ways, so breaking old code for this issue as well is not as big a deal.
Anyway, the proper way to fix your code is either to add a __new__
method to Root
that accepts and suppresses the value
argument (e.g. it doesn't pass it on to object.__new__
), or to change Sub
so that it doesn't pass the value to its parent at all (e.g. it just calls super(Sub, cls).__new__(cls)
). You might want to think a bit about whether you actually need both __new__
and __init__
methods in Sub
, since most classes only need to override one of them.
Upvotes: 6