Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how do I properly inherit from a superclass that has a __new__ method?

Let's assume we have a class 'Parent' , that for some reason has __new__ defined and a class 'Child' that inherits from it. (In my case I'm trying to inherit from a 3rd party class that I cannot modify)

class Parent:
    def __new__(cls, arg):
        # ... something important is done here with arg

My attempt was:

class Child(Parent):
    def __init__(self, myArg, argForSuperclass):
         Parent.__new__(argForSuperclass)
         self.field = myArg

But while

p = Parent("argForSuperclass")

works as expected

c = Child("myArg", "argForSuperclass")

fails, because 'Child' tries to call the __new__ method it inherits from 'Parent' instead of its own __init__ method.

What do I have to change in 'Child' to get the expected behavior?

like image 515
PeterE Avatar asked May 28 '12 18:05

PeterE


People also ask

What is __ new __ in Python?

In the base class object , the __new__ method is defined as a static method which requires to pass a parameter cls . cls represents the class that is needed to be instantiated, and the compiler automatically provides this parameter at the time of instantiation.

What is super () __ Init__?

When you initialize a child class in Python, you can call the super(). __init__() method. This initializes the parent class object into the child class. In addition to this, you can add child-specific information to the child object as well.

How do you call a super method in Python?

Using Super(): Python super() function provides us the facility to refer to the parent class explicitly. It is basically useful where we have to call superclass functions. It returns the proxy object that allows us to refer parent class by 'super'.

How do you inherit from another class?

When you define a class to derive from another class, the derived class implicitly gains all the members of the base class, except for its constructors and finalizers. The derived class reuses the code in the base class without having to reimplement it. You can add more members in the derived class.


2 Answers

Firstly, it is not considered best practice to override __new__ exactly to avoid these problems... But it is not your fault, I know. For such cases, the best practice on overriding __new__ is to make it accept optional parameters...

class Parent(object):
    def __new__(cls, value, *args, **kwargs):
        print 'my value is', value
        return object.__new__(cls, *args, **kwargs)

...so children can receive their own:

class Child(Parent):
    def __init__(self, for_parent, my_stuff):
        self.my_stuff = my_stuff

Then, it would work:

>>> c = Child(2, "Child name is Juju")
my value is 2
>>> c.my_stuff
'Child name is Juju'

However, the author of your parent class was not that sensible and gave you this problem:

class Parent(object):
    def __new__(cls, value):
        print 'my value is', value
        return object.__new__(cls)

In this case, just override __new__ in the child, making it accept optional parameters, and call the parent's __new__ there:

class Child(Parent):
    def __new__(cls, value, *args, **kwargs):
        return Parent.__new__(cls, value)
    def __init__(self, for_parent, my_stuff):
        self.my_stuff = my_stuff
like image 125
brandizzi Avatar answered Sep 30 '22 08:09

brandizzi


Child doesn't call Parent's __new__ instead of its own __init__, it calls it's own __new__ (inherited from Parent) before __init__.

You need to understand what these methods are for and when Python will call them. __new__ is called to come up with an instance object, and then __init__ is called to initialize the attributes of that instance. Python will pass the same arguments to both methods: the arguments passed to the class to begin the process.

So what you do when you're inheriting from a class with __new__ is exactly what you do when inheriting any other method which has a different signature in the parent than you want it to have. You need to override __new__ to receive Child's arguments and call Parent.__new__ with the arguments it expects. Then you override __init__ entirely separately (though with the same argument list, and the same need to call Parent's __init__ with its own expected argument list).

There are two potential difficulties that could get in your way, however, because __new__ is for customising the process of obtaining a new instance. There's no reason it has to actually create a new instance (it could find one that already exists), and indeed it doesn't even have to return an instance of the class. This can lead to the following issues:

  1. If __new__ returns an existing object (say, because Parent expects to be able to cache its instances), then you may find your __init__ being called on already existing objects, which can be very bad if your objects are supposed to have changeable state. But then, if Parent expects to be able to do this sort of thing it will probably expect a bunch of constraints on how it is used, and sub-classing it in ways that arbitrarily break those constraints (e.g. adding mutable state to a class that expects to be immutable so it can cache and recycle its instances) is almost certainly not going to work even if you can get your objects initialised correctly. If this sort of thing is going on, and there isn't any documentation telling you what you should be doing to subclass Parent, then the third-party probably doesn't intend you to be subclassing Parent, and things are going to be difficult for you. Your best bet though would probably be to try moving all of your initialisation to __new__ rather than __init__, ugly as that is.

  2. Python only calls the __init__ method if the class' __new__ method actually returns an instance of the class. If Parent is using its __new__ method as some sort of "factory function" to return objects of other classes, then subclassing in a straightforward fashion is very likely to fail, unless all you need to do is change how the "factory" works.

like image 38
Ben Avatar answered Sep 30 '22 10:09

Ben