I have the following classes defined:
class First(object):
def __init__(self):
print("first")
def mF1(self):
print "first 1"
def mF2(self):
print "first 2"
class Second(object):
def __init__(self):
print("second")
def mS1(self):
print "second 1"
class Third(object):
def __init__(self):
print("third")
def mT1(self):
print "third 1"
def mT2(self):
print "third 2"
def mT3(self):
print "third 3"
class Fourth(First, Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print("fourth")
C = Fourth()
C.mF1()
C.mF2()
C.mS1()
C.mT1()
C.mT2()
C.mT3()
Which gives the output:
first
fourth
first 1
first 2
second 1
third 1
third 2
third 3
With this, it is pretty clear that all the attributes and methods of classes First
, Second
, Third
and Fourth
is available in the class Fourth
.
Now, I would like class Fourth
to selectively inherit from parents based on the context - i.e., from First
alone or from First
and Third
etc. One way is to have separate classes defined as follows:
class Fourth1(First):
def __init__(self):
super(Fourth, self).__init__()
print("fourth first")
class Fourth2(First, Third):
def __init__(self):
super(Fourth, self).__init__()
print("fourth first third")
which means having separate classes defined and having separate class names instead of one.
I want to know if there is an easier, dynamic and "pythonic" way to achieve this? Is it possible to choose where to inherit (just like super()
does, inherit all attributes and methods including the private methods) from in a simple way like say,
C = Fourth(1,0,0)
to inherit from First
and
C = Fourth(1,0,1)
to inherit from First
and Third
?
It can be done via __new__
. Because it is possible to dynamically create classes inheriting from other classes, and __new__
can create objects of arbitrary types:
class Fourth(object):
"""BEWARE special class that creates objects of subclasses"""
classes = [None] * 8
def __new__(cls, first, second, third):
index = 1 if first else 0
index += 2 if second else 0
index += 4 if third else 0
if not cls.classes[index]:
parents = [Fourth]
if first: parents.append(First)
if second: parents.append(Second)
if third: parents.append(Third)
ns = {'__new__': object.__new__,
'__init__': Fourth._child__init__,
'__doc__': Fourth.__doc__}
cls.classes[index] = type('Fourth', tuple(parents), ns)
return object.__new__(cls.classes[index])
def _child__init__(self, first = None, second=None, third=None):
Fourth.__init__(self)
def __init__(self):
print("Fourth")
After that you can do what you wanted:
>>> c = Fourth(1,0,0)
Fourth
>>> c2 = Fourth(1,1,1)
Fourth
>>> c
<__main__.Fourth1 object at 0x0000024026151780>
>>> c2
<__main__.Fourth7 object at 0x000002402616E8D0>
>>> c2.mT1()
third 1
>>> c.mT1()
Traceback (most recent call last):
File "<pyshell#302>", line 1, in <module>
c.mT1()
AttributeError: 'Fourth1' object has no attribute 'mT1'
But I strongly advise you against that hack unless you have important reasons to do so. Because it ends in having a class (Fourth
) that creates objects not from itself but from subclasses. And object apparently belonging to that class will have different behaviour. This will disturb future readers and maintainers