1

In python, is there a way, when initializing a Class, to change the superclass in function of the value of a class attribute? Here's an example of what I want to do. First I have theses classes:

class A(object):
 pass
class B(A):
 # extend and override class A
 pass
class C(A or B):
 # extend and override class A
 pass

Secondly, I want to create other classes that inherit from Class C but in some cases I want C to inherit from A and on other cases, inherit from B:

class D(C):
 # C inherit only from A
 from_B = False
class E(C):
 # C inherit from B because attribute from_B = True
 from_B = True

I tried with metaclass but that was setting the base class of C (to A or B) for all subclasses (D, E, ...) at the initialization of the first subclass. So, if the first subclass to be initialize had from_B = True, all subclasses of C had C(B) as parent whatever from_B was set. My code was something like this:

class MetaC(type):
 def __new__(cls, name, bases, attrs):
 if C in bases and getattr(attrs, 'from_B', False):
 C.__bases__[C.__bases__.index(A)] = B
 return super(MetaC, cls).__new__(cls, name, bases, attrs)
class C(A):
 __metaclass__ = MetaC

My goal is to avoid the duplication of the code of the C class and keeping the possibility to have or not the added functionalities of the B class. I should mention that I don't have control on A and B classes.

UPDATE

I think I got it with this metaclass (code is a bit rough at the moment):

class MetaC(type):
 def __new__(cls, name, bases, attrs):
 for base in bases:
 if base.__name__ == 'C':
 if attrs.has_key('from_B'):
 list_bases = list(base.__bases__)
 list_bases[list_bases.index(A)] = B
 base.__bases__ = tuple(list_bases)
 elif B in base.__bases__:
 list_bases = list(base.__bases__)
 list_bases[list_bases.index(B)] = A
 base.__bases__ = tuple(list_bases)
 break
 return super(MetaC, cls).__new__(cls, name, bases, attrs)

UPDATE 2

This solution doesn't work because I'm always modifying the base class C. So, when a subclass is instanciated it will use the C class in it's current state.

asked Mar 22, 2011 at 3:26
5
  • 1
    Why? What problem are you trying to solve that requires something like this? Commented Mar 22, 2011 at 3:32
  • Extending admin.modelAdmin (A in my example) in Django. Sometimes my admin classes (D and E), that inherit from my main admin class (C), need also the functionalities of other packages that extend and override admin.ModelAdmin like reversion, ForeignKeyAutocomplete, etc. (B). But it's not the first time I have this use case. Multipleinheritance can be tricky in this case. Commented Mar 22, 2011 at 3:44
  • 2
    Are you familiar with the mixin approach? (If not, I'll spin out an answer, but I suspect you may have thought of it already.) Commented Mar 22, 2011 at 3:50
  • Hmm, I know the general concept of mixin but spin out your answer please. If I'm right, Mixin use multiple inheritance and, in my case, I'm not in control of one of the class in the mix so I can't be sure that everything will be call correctly (with super()). But I can misunderstand something. So go ahead! Commented Mar 22, 2011 at 4:01
  • Ah, okay. I went to type out the answer but I realised that a mixin probably won't get you anything more than straight-out multiple inheritance would anyway. Nuts. Commented Mar 22, 2011 at 4:13

2 Answers 2

1

I ended by using cooperative multiple inheritance. It works fine. The only drawback is that we need to be sure that for methods that need to be call on many parent classes (like methods that are present in A and B and C), there's a super() call in each method definitions of each classes and that they have the same calling signature in every case. Fortunately for me my B classes respect this.

Example:

class A(object):
 some_method(arg1, arg2, karg1=None):
 do_some_stuff(arg1, arg2, karg1)
class B(A):
 # extend and override class A
 some_method(arg1, arg2, karg1=None):
 super(B, self).some_method(arg1, arg2, karg1)
 do_more_stuff(arg1, arg2, karg1)
class C(A, B):
 # extend and override class A
 some_method(arg1, arg2, karg1=None):
 do_other_stuff(arg1, arg2, karg1)
 super(C, self).some_method(arg1, arg2, karg1)

This way, when some_method will be call from C or C childrens, all theses calls will be made in this order:

  • C.some_method
  • A.some_method
  • B.some_method

Check The wonders of cooperative inheritance for more info on the subject.

answered Mar 27, 2011 at 1:55
Sign up to request clarification or add additional context in comments.

Comments

0

This looks so painful, you have to consider composition/delegation instead of contorting inheritance this way. What do you think of something like this?

class A(object):
 def from_B(self):
 return False
class B(object):
 def from_B(self):
 return True
class C(object):
 pass
class PolyClass(object):
 def __init__(self, *args):
 self.delegates = [c() for c in args[::-1]]
 def __getattr__(self, attr):
 for d in self.delegates:
 if hasattr(d, attr):
 return getattr(d,attr)
 raise AttributeError(attr + "? what the heck is that?")
 def __repr__(self):
 return "<instance of (%s)>" % ','.join(d.__class__.__name__ 
 for d in self.delegates[::-1])
pc1 = PolyClass(A,B)
pc2 = PolyClass(A,C)
pc3 = PolyClass(B,C)
for p in (pc1,pc2,pc3):
 print p, p.from_B()
print pc1.from_C()

Prints:

<instance of (A,B)> True
<instance of (A,C)> False
<instance of (B,C)> True
Traceback (most recent call last):
 File "varying_delegation.py", line 33, in <module>
 print pc1.from_C()
 File "varying_delegation.py", line 21, in __getattr__
 raise AttributeError(attr + "? what the heck is that?")
AttributeError: from_C? what the heck is that?

EDIT: Here's how to take the not-in-your-control classes A and B, and create custom C classes that look like they extend either an A or a B:

# Django admin classes
class A(object):
 def from_B(self):
 return False
class B(A):
 def from_B(self):
 return True
# Your own class, which might get created with an A or B instance
class C(object):
 def __init__(self, obj):
 self.obj = obj
 def __getattr__(self, attr):
 return getattr(self.obj, attr)
# these are instantiated some way, not in your control
a,b = A(), B()
# now create different C's
c1 = C(a)
c2 = C(b)
print c1.from_B()
print c2.from_B()

prints:

False
True

And to create your subclasses D and E, create an interim subclass of C (I called it SubC cause I lack imagination), which will auto-init the C superclass with a specific global variable, either a or b.

# a subclass of C for subclasses pre-wired to delegate to a specific 
# global object
class SubC(C):
 c_init_obj = None
 def __init__(self):
 super(SubC,self).__init__(self.c_init_obj)
class D(SubC): pass
class E(SubC): pass
# assign globals to C subclasses so they build with the correct contained 
# global object
D.c_init_obj = a
E.c_init_obj = b
d = D()
e = E()
print d.from_B()
print e.from_B()

Again, prints:

False
True
answered Mar 22, 2011 at 4:33

3 Comments

Thanks for the answer. But I don't know how I could apply composition/delegation in the context of the Django admin classes? I have no control on instanciation of this classes and class B and C are extending methods of class A. Or I'm missing something...
Looking more closely at this, I would suggest making C a wrapper around an A or a B, and delegate calls to the contained object. D and E sound more like instances than subclasses. You are trying to force too much into inheritance - with getattr, Python makes containment/delegation very easy. I'll edit my answer to show how C can contain A or B.
Interesting approach but unfortunately I could not apply it to my case because first, I have no control of the instantiation of any of this classes, secondly because I need to extend (in C) some methods of A, and thirdly D and Eneed to be subclass of A for the admin in Django to work.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.