9

This is a feature I miss in several languages and wonder if anyone has any idea how it can be done in Python.

The idea is that I have a base class:

class Base(object):
    def __init__(self):
        self.my_data = 0
    def my_rebind_function(self):
        pass

and a derived class:

class Child(Base):
    def __init__(self):
        super().__init__(self)
        # Do some stuff here
        self.my_rebind_function() # <==== This is the line I want to get rid of
    def my_rebind_function(self):
        # Do stuff with self.my_data

As can be seen above, I have a rebound function which I want called after the Child.__init__ has done its job. And I want this done for all inherited classes, so it would be great if it was performed by the base class, so I do not have to retype that line in every child class.

It would be nice if the language had a function like __finally__, operating similar to how it operates with exceptions. That is, it should run after all __init__-functions (of all derived classes) have been run, that would be great. So the call order would be something like:

Base1.__init__()
...
BaseN.__init__()
LeafChild.__init__()
LeafChild.__finally__()
BaseN.__finally__()
...
Base1.__finally__()

And then object construction is finished. This is also kind of similar to unit testing with setup, run and teardown functions.

5
  • you want to override this method in the children classes ? Commented Jul 9, 2017 at 21:02
  • No, I want another function called after all the children's init-functions have been called. Commented Jul 9, 2017 at 21:09
  • You could do that with a metaclass, albiet more complex :\ Commented Jul 9, 2017 at 21:31
  • If you put the self.my_rebind_function() in the base class __init__(), it will call the derived class version of my_rebind_function() when it has been called by the derived class's __init__(). Commented Jul 9, 2017 at 21:54
  • @martineau: yes sure, but that is not what I want. I want it called after the derived class __init__-method. Commented Jul 9, 2017 at 21:56

3 Answers 3

8

You can do this with a metaclass like that:

    class Meta(type):
        def __call__(cls, *args, **kwargs):
            print("start Meta.__call__")
            instance = super().__call__(*args, **kwargs)
            instance.my_rebind_function()
            print("end Meta.__call__\n")
            return instance


    class Base(metaclass=Meta):
        def __init__(self):
            print("Base.__init__()")
            self.my_data = 0

        def my_rebind_function(self):
            pass


    class Child(Base):
        def __init__(self):
            super().__init__()
            print("Child.__init__()")

        def my_rebind_function(self):
            print("Child.my_rebind_function")
            # Do stuff with self.my_data
            self.my_data = 999


    if __name__ == '__main__':
        c = Child()
        print(c.my_data)

By overwriting Metaclass.__call__ you can hook after all __init__ ( and __new__) methods of the class-tree have run an before the instance is returned. This is the place to call your rebind function. To understand the call order i added some print statements. The output will look like this:

start Meta.__call__
Base.__init__()
Child.__init__()
Child.my_rebind_function
end Meta.__call__

999

If you want to read on and get deeper into details I can recommend following great article: https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/

Sign up to request clarification or add additional context in comments.

1 Comment

This does not seem to work in some cases where the new method has also been overriden in the metaclass (the call method never seems to get called in that case, at least I think I could provide an example), any clue or workaround for that case?
2

I may still not fully understand, but this seems to do what I (think) you want:

class Base(object):
    def __init__(self):
        print("Base.__init__() called")
        self.my_data = 0
        self.other_stuff()
        self.my_rebind_function()

    def other_stuff(self):
        """ empty """

    def my_rebind_function(self):
        """ empty """

class Child(Base):
    def __init__(self):
        super(Child, self).__init__()

    def other_stuff(self):
        print("In Child.other_stuff() doing other stuff I want done in Child class")

    def my_rebind_function(self):
        print("In Child.my_rebind_function() doing stuff with self.my_data")

child = Child()

Output:

Base.__init__() called
In Child.other_stuff() doing other stuff I want done in Child class
In Child.my_rebind_function() doing stuff with self.my_data

2 Comments

This solves the problem and is quite nice, but there is one issue with this solution. Now I have to have the other_stuff-method, which is really a replacement for the __init_-method in the Child-class. So, now the user of the Child-class has to move code from its normal place (__init__), to some new method (other_stuff)..
The user of the Child-class won;t be writing any of its code. The implementer of it will simply have to put the needed code in a different place (method). Doesn't sound like a big deal to me...
0

If you want a "rebind" function to be invoked after each instance of a type which inherits from Base is instantiated, then I would say this "rebind" function can live outside the Base class(or any class inheriting from it).

You can have a factory function that gives you the object you need when you invoke it(for example give_me_a_processed_child_object()). This factory function basically instantiates an object and does something to it before it returns it to you.

Putting logic in __init__ is not a good idea because it obscures logic and intention. When you write kid = Child(), you don't expect many things to happen in the background, especially things that act on the instance of Child that you just created. What you expect is a fresh instance of Child.

A factory function, however, transparently does something to an object and returns it to you. This way you know you're getting an already processed instance.

Finally, you wanted to avoid adding "rebind" methods to your Child classes which you now you can since all that logic can be placed in your factory function.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.