Controlling a child class method from a parent class

Let's say I have this code:

class Foo:
    def write(self, s=""):

        # Make sure that overwritten
        # 'write' method in child class
        # does what it specified, and
        # then what comes next...

        print "-From Foo"

class Bar(Foo):
    def write(self, s=""):
        print s

baz = Bar()
baz.write("Hello, World!")

      

The last call obviously outputs the hello world on its own. I need to get it to write "-From Foo", but without changing the Bar class, just the Foo class. I have tried __bases__

other things as well, but it doesn't work for my purpose.

+3


source to share


4 answers


I 100% agree with Lattyware: you shouldn't be doing this. Parent classes do not need to "know" about subclasses or how they work.

But I have to say that magic can be used __getattribute__

:

class Foo(object):
    def __getattribute__(self, attr):
        if attr != 'write':
            return super(Foo, self).__getattribute__(attr)
        meth = super(Foo, self).__getattribute__(attr)
        if meth.im_func is Foo.write.im_func:
            # subclass does not override the method
            return meth

        def assure_calls_base_class(*args, **kwargs):
            meth(*args, **kwargs)
            Foo.write(self, *args, **kwargs)
        return assure_calls_base_class

    def write(self, s=""):
        print "-From Foo"



class Bar(Foo):
    def write(self, s=""):
        print s

      

Running the code:



>>> b = Bar()
>>> b.write('Hello, World!')
Hello, World!
-From Foo

      

Note that this is easy to hack and will probably break when using the inheritance bit, or even if you access write

from a class:

>>> Bar.write(b, 'Hello, World!')  #should be equivalent to b.write('Hello, World!')
Hello, World!

      

+4


source


There is no (good) way to do this without changing Bar()

- what you want to do is use super()

internally Bar()

, this will allow you to call the parent method.

If you're using a class that you can't change that doesn't, the best solution is to create a wrapper class that does what you want manually, using a class that doesn't play well. For example:



class BarWrapper(Foo):
    def __init__(self, *args, **kwargs):
        self.bar = Bar(*args, **kwargs)

    def write(self, *args, **kwargs):
        super(BarWrapper, self).write(*args, **kwargs)
        self.bar.write(*args, **kwargs)

      

(Naturally, you will need more, depending on how much for your class, and note 3.x, you can use a simpler syntax for super()

by dropping the arguments.)

+3


source


This is one way to do it using metaclass magic; IMHO, it is more robust and flexible than other approaches, it also handles unbounded invocation (for example Bar.write(x, "hello")

) and unidirectional inheritance (see the Baz section below):

class ReverserMetaclass(type):
    def __new__(cls, name, bases, dct):
        """ This metaclass replaces methods of classes made from it
            with a version that first calls their base classes
        """
        # create a new namespace for the new class
        new_dct = {}
        for member_name, member in dct.items():
            # only decorate methods/callable in the new class
            if callable(member):
                member = cls.wrap(bases, member_name, member)
            new_dct[member_name] = member
        # construct the class
        return super(ReverserMetaclass, cls).__new__(cls, name, bases, new_dct)

        # instead of the above, you can also use something much simpler
        #     dct['read'] = cls.wrap(bases, 'read', dct['read'])
        #     return super(ReverserMetaclass, cls).__new__(cls, name, bases, dct)
        # if you have a specific method that you want to wrap and want to 
        # leave the rest alone

    @classmethod
    def wrap(cls, bases, name, method):
        """ this method calls methods in the bases before calling the method """
        def _method(*args, **kwargs):
            for base in bases:
                if hasattr(base, name):
                    getattr(base, name)(*args, **kwargs)
            # put this above the loop if you want to reverse the call order
            ret = method(*args, **kwargs)
            return ret
        return _method

      

An example of starting the console:

>>> class Foo(object):
...     __metaclass__ = ReverserMetaclass
...     def write(self, s=""):
...         # Make sure that overwritten
...         # 'write' method in child class
...         # does what it specified, and
...         # then what comes next...
...         print "Write - From Foo", s
...     def read(self):
...         print "Read - From Foo"
...
>>> class Bar(Foo):
...     def write(self, s=""):
...         print "Write - from Bar", s
...     def read(self):
...         print "Read - From Bar"
...
>>> class Baz(Bar):
...     def write(self, s=""):
...         print "Write - from Baz", s
...
>>> x = Bar()
>>> x.write("hello")
Write - From Foo hello
Write - from Bar hello
>>> Bar.read(x)
Read - From Foo
Read - From Bar
>>>
>>> x = Baz()
>>> x.read()
Read - From Foo
Read - From Bar
>>> x.write("foo")
Write - From Foo foo
Write - from Bar foo
Write - from Baz foo

      

Metaclass Python is extremely powerful, although as others have said, you really don't want to do this magic too often.

+3


source


Here's another way to do it with a metaclass. An important advantage it has when used is __getattribute__()

that there is no additional overhead for accessing or using other attributes and methods of the subclass. It also supports single inheritance when subclasses are defined.

class Foo(object):
    class __metaclass__(type):
        def __new__(metaclass, classname, bases, classdict):
            clsobj = super(metaclass, metaclass).__new__(metaclass, classname, 
                                                         bases, classdict)
            if classname != 'Foo' and 'write' in classdict:  # subclass?
                def call_base_write_after(self, *args, **kwargs):
                    classdict['write'](self, *args, **kwargs)
                    Foo.write(self, *args, **kwargs)

                setattr(clsobj, 'write', call_base_write_after)  # replace method

            return clsobj

    def write(self, s=""):
        print "-From Foo"

class Bar(Foo):
    def write(self, s=""):
        print 'Bar:', s

class Baz(Bar):  # sub-subclass
    def write(self, s=""):
        print 'Baz:', s

Bar().write('test')
Baz().write('test')

      

Output:

Bar: test
-From Foo
Baz: test
-From Foo

      

If you want the subclass methods to write()

call their base class version afterwards instead of the root ( Foo

) class , just change the hardcoded:

    Foo.write(self, *args, **kwargs)

      

call:

    super(clsobj, self).write(*args, **kwargs)

      

in Foo.__new__()

.

+1


source







All Articles