I've found myself in an unusual situation where I need to change the MRO of a class at runtime.
The code:
class A(object):
def __init__(self):
print self.__class__
print "__init__ A"
self.hello()
def hello(self):
print "A hello"
class B(A):
def __init__(self):
super(B, self).__init__()
print "__init__ B"
self.msg_str = "B"
self.hello()
def hello(self):
print "%s hello" % self.msg_str
a = A()
b = B()
As to be expected, this fails as the __init__
method of A (when called from B) calls B's hello
which attempts to access an attribute before it exists.
The issue is that I'm constrained in the changes I can make:
- B must subclass A
- A cannot be changed
- Both A and B require a hello method
- B cannot initialise other attributes before calling the super
__init__
I did solve this conceptually, by changing the MRO at runtime. In brief, during B's __init__
, but before calling super __init__
, the MRO would be changed so that A would be searched for methods first, thereby calling A's hello
instead of B's (and therefore failing).
The issue is that MRO is read only (at class runtime).
Is there another way to implement this ? Or possibly a different solution altogether (that still respects the aforementioned constraints) ?
The other provided answers are advisable if you are not bound by the constraints mentioned in the question. Otherwise, we need to take a journey into mro hacks and metaclass land.
After some reading, I discovered you can change the mro of a class, using a metaclass.
This however, is at class creation time, not at object creation time. Slight modification is necessary.
The metaclass provides the
mro
method, which we overload, that is called during class creation (the metaclass'__new__
call) to produce the__mro__
attribute.The
__mro__
attribute is not a normal attribute, in that:__new__
callHowever, it appears to be recalculated (using the
mro
method) when a class' base is changed. This forms the basis of the hack.In brief:
B
) is created using a metaclass (change_mro_meta
). This metaclass provides:__mro__
attributechange_mro
) to control the mro behaviourAs mentioned, modifying the mro of a class while in its
__init__
is not thread safe.The following may disturb some viewers. Viewer discretion is advised.
The hack:
Some notes:
The
hack_mro
,fix_mro
andrecalc_mro
methods are staticmethods to the metaclass but classmethods to the class. It did this, instead of multiple inheritance, because I wanted to group the mro code together.The
mro
method itself returns the default ordinarily. Under the hack condition, it appends the second element of the default mro (the immediate parent class) to the mro, thereby causing the parent class to see its own methods first before the subclass'.I'm unsure of the portability of this hack. Its been tested on 64bit CPython 2.7.3 running on Windows 7 64bit.
Don't worry, I'm sure this won't end up in production code somewhere.
There may be grander solutions but a simple option is to write class B defensively. For example:
A good editor with regex capability could auto-insert appropriate
if not hasattr(self, 'some_flag'):...
lines as the first lines of any methods in B.I'd like to point out a solution which is very specific to the example you present in your question, and therefor unlikely to help. (But in case it does help at all...)
You can bypass
hello
's polymorphism by defining it as a class member, instead of a method.(
A
remains unchanged).This solution will break if:
B
and need to overridehello
in the subclassmsg_str
is modified after__init__
runsFor your particular example, one solution is to give B a class attribute to hold a default message:
Usually this causes confusion, but in this case it might be helpful. If
B.hello
is called before the instance'smsg_str
is set, it will read the class one. Once the instancemsg_str
is set, it shadows the class one so that future accesses ofself.msg_str
will see the instance-specific one.I don't quite understand why you can't set attributes before calling the superclass
__init__
. Depending on exactly what the underlying situation is, there may be other solutions.My solution would be to ask for forgiveness:
or if you do not want to refactor methods called from init:
I don't know if it's relevant to the specific problem, but it seems to me that changing the MRO on the fly like that could be risky in a concurrent program, and could definitely have issues if any of these objects turn out to be created recursively.
A non-MRO-based solution occurs to me, depending on the nature of the errors this code would have encountered. (Keeping in mind that this is belated. Perhaps somebody else will want a different answer.)
Basically, each hello() method on B would be wrapped in a decorator. Something along the lines of
If you don't want to go the descriptor route, it's also possible to do something like