Daenyth
Daenyth

Reputation: 37461

"TypeError: object() takes no parameters" With python2 metaclass converted to python3

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

Answers (1)

Blckknght
Blckknght

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

Related Questions