7

I'm migrating some code from Python 2 to Python 3, and I am getting different behaviour. Looking through the lists of "what's changed" hasn't pointed me to any relevant differences, but presumably I've missed a big one.

I have simplified my code as much as possible to get this 'minimal faulty program':

def decorator(Type):
    """ This is a class decorator. It replaces a class with a subclass which
    *should be* equivalent.

    The result works on Python 2.7 but not on Python 3.4. """

    class FactorySubclass(Type):
        """ This subclasses from the provided type, and overrides the __new__
            and __init__ methods, but replaces them with exact equivalents,
            so I can't see how this has any effect. """

        def __new__(cls, *args, **kwargs):
            # Simplified this code to do basically nothing.
            # If this line is removed, it works on both versions.
            return Type.__new__(cls, *args, **kwargs)

        def __init__(self, *args, **kwargs):
            # Simplified this code to do basically nothing.
            Type.__init__(self, *args, **kwargs)

    return FactorySubclass


@decorator
class ExampleClass(object):
    def __init__(self, param=3):
        print("Constructed example instance")


ec = ExampleClass(param=5)

This code runs, and prints Constructed example instance in Python 2.7. This code fails, and dumps a stack trace in Python 3.4.

Traceback (most recent call last):
  File "mfp.py", line 31, in <module>
    ec = ExampleClass(param=5)
  File "mfp.py", line 16, in __new__
    return Type.__new__(cls, *args, **kwargs)
TypeError: object() takes no parameters

Typically this error means that someone has misspelled __init__ (and so constructor parameters are bypassing the relevant class and being given to object's parameterless constructor, but that doesn't seem to be the case here.

Oh, and as an afterthought, I confirmed that, yes, the value of param was 5 in Python 2.7.

2to3 gives it a clean-bill of health.

Please give me a pointer to a change in Python 3 that would invalidate this code, so I may read more about it.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Oddthinking
  • 24,359
  • 19
  • 83
  • 121
  • This looks like a dupe of [Why does object.\_\_new\_\_ work differently in these three cases](http://stackoverflow.com/q/19277399), I'll investigate. – Martijn Pieters Oct 30 '14 at 12:52
  • Yes, this is caused by `ExampleClass` not having a `__new__` method of its own. – Martijn Pieters Oct 30 '14 at 12:58
  • possible duplicate of [Why does object.\_\_new\_\_ work differently in these three cases](http://stackoverflow.com/questions/19277399/why-does-object-new-work-differently-in-these-three-cases) – Martijn Pieters Oct 30 '14 at 12:58
  • @Martijn: Given the behaviour around there has been changed, it seems likely to be related. However your answer states "Python will only complain about `__init__` not supporting arguments if neither `__new__` nor `__init__` have been overridden". In this case, both have been overridden. – Oddthinking Oct 30 '14 at 12:59
  • Add a `__new__` method to `Example` and the error goes away. – Martijn Pieters Oct 30 '14 at 13:00
  • (Thinks out aloud): The (predecorated) Example class has no `__new__`, but the generated subclass does. The generated subclass calls the raw Example class, which doesn't... and so it passes it up to object() which now complains. Yes, you are right. Want to turn it into an answer to get accepted? – Oddthinking Oct 30 '14 at 13:01

1 Answers1

2

You have the answer in your question:

Typically this error means [...] constructor parameters are [...] being given to object's parameterless constructor [...]

To fix, change your decorator to only add __init__ and __new__ if the passed-in Type has those methods.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237