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.
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!
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.)
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.
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__()
.