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