I'm trying the super() function as described in Python Cookbook, 1st ed, p. 172 (Recipe 5.4).
class A(object): def f(self): print 'A'
class B(object): def f(self): print 'b'
class C(A,B): def f(self): super(c,self).f() print 'C'
def test(cls): x = cls() x.f()
test(C)
I have the impression that this is supposed to call the f method in both A and B, so it should print A B C or maybe B A C depending on the resolution order. However, it only calls A.f and not B.f.
I also notice that if I say
class B(object): def f(self): super(B,self).f() print 'b'
then test(B) raises an exception since B has no superclass with an f method. That doesn't seem like such a good thing necessarily.
Anyway, is there a preferred way of writing this example so that C.f automatically calls both A.f and B.f?
It would be nice to make some decorators to do CLOS-like automatic method combination, something like:
class C(A,B): @aftermethod def f(self): print 'C'
test(C)
would call A.f and B.f and then call C.f.
class C(A,B): @beforemethod def f(self): print 'C'
test(C)
would call C.f and then call A.f and B.f. This would not be exactly the same as calling super, since it should not be an error to call a beforemethod or aftermethod when the superclass doesn't have its own method for that operation. (I'm not sure how CLOS does this. I've played with Flavors (a forerunner of CLOS) but have never actually used CLOS).
>then > test(B) >raises an exception since B has no superclass with an f method.
Correct. When you use super(B,self) it accesses the current instance as the class B. If it has no method named 'f' then this will end up in an exception.
>That doesn't seem like such a good thing necessarily.
But yes, it is. When you try to call a nonexistent method, it should raise an exception.
>Anyway, is there a preferred way of writing this example so that C.f >automatically calls both A.f and B.f?
I do not know a preferred way. However, I made this example for you, I hope it helps.
class CallSupersMixin(object): def callsupers(self,fname,*args,**kwargs): l = self.__class__.__bases__ for cls in l: if hasattr(cls,fname): getattr(cls,fname)(self,*args,**kwargs) elif cls == CallSupersMixin: pass else: raise AttributeError("Base class %s does not have a method named %s " % ( str(cls),fname ) )
class A(object): def f(self): print 'A.f called'
class B(object): def f(self): print 'B.f called'
class AB(A,B,CallSupersMixin): def f(self,*args,**kwargs): self.callsupers('f',*args,**kwargs)
ab = AB() ab.f()
Of course you can remove the "raise AttributeError" part. Then it will call only the classes that have the given method. I know it is not a very good example but you can go from here. Best,
Laci 2.0
-- _________________________________________________________________ Laszlo Nagy web: http://designasign.biz IT Consultant mail: gand...@geochemsource.com
> I have the impression that this is supposed to call the f method > in both A and B, so it should print > A > B > C > or maybe > B > A > C > depending on the resolution order. However, it only calls A.f and not > B.f.
You misunderstand. A single call to super only calls the next method in the chain. You have to include calls to super at all levels except for the ultimate base class.
>The trick is that C.f only calls A.f, but A.f needs to end up calling B.f >when it is used in a C.
I believe your response only applies to single inheritance. For classes with muliple bases classes, you need to call the base methods one by one.
BTW I prefer to call the base methods in this form:
class AB(A,B): def f(self): A.f(self) B.f(self)
This arises the question: is there a difference between these:
super(A,self).f() # I do not use to use this.... A.f(self)
-- _________________________________________________________________ Laszlo Nagy web: http://designasign.biz IT Consultant mail: gand...@geochemsource.com
> It would be nice to make some decorators to do CLOS-like > automatic
method combination ...
You can't do that with decorators (I mean the automatic call of the supermethod) but you can with a metaclass. There is an example in my ACCU lectures:
You can also define a custom super that does not give an error when the superclass doesn't have the corresponding method (I posted an example some time ago to somebody complaining for the same reason).
>>The trick is that C.f only calls A.f, but A.f needs to end up calling >>B.f when it is used in a C.
> I believe your response only applies to single inheritance. For > classes with muliple bases classes, you need to call the base methods > one by one.
super wouldn't be much use if it only applied to single inheritance.
If you have a class hierarchy:
class Base(object): class A(Base): class B(Base): class AB(A, B):
and each class has a method 'f' then in an instance of AB:
> BTW I prefer to call the base methods in this form:
> class AB(A,B): > def f(self): > A.f(self) > B.f(self)
Which is fine so long as nobody else tries to add further subclasses later:
class C(B): ... class Mine(AB,C): ...
Mine().f()
Using super throughout this works (it calls f in Mine, AB, A, C, B, and then Base), but your explicit call to the base classes means that if you don't call C.f() explicitly from Mine it never gets called, and if you do call it explicitly from Mine it gets called *after* B.f() has been called (and B.f() probably ends up being called twice).
> This arises the question: is there a difference between these:
> super(A,self).f() # I do not use to use this.... > A.f(self)
They are totally different. Super passes the call along to the next method in the defined method resolution order (MRO): the only thing you can be sure of here is that super(A,self).f() will never call the method f defined in class A (whereas A.f() calls the method f defined in class A or one of its base classes).
I haven't been careful here to only replace functions with functions. That is, I probably should make a real Null object that acts both like the null function above and like the None object otherwise. But as long as you're only using mysuper to get functions, this should work ok. Personally, I would probably do what others have suggested and add a base class with an f method from which A and B derive, but if that's not an option, you can play around with subclassing super and probably get something like what you want.
>Which is fine so long as nobody else tries to add further subclasses later:
>class C(B): ... >class Mine(AB,C): ...
>Mine().f()
>Using super throughout this works (it calls f in Mine, AB, A, C, B, and >then Base), but your explicit call to the base classes means that if you >don't call C.f() explicitly from Mine it never gets called, and if you do >call it explicitly from Mine it gets called *after* B.f() has been called >(and B.f() probably ends up being called twice).
Okay, I understand now. It was a good learning session for me. :-) At this moment I do not see a problem where I would need a diamond shaped inheritance graph but I'll keep in mind the difference between super and direct calls. :-)
I tested this and I realized that if you change the parameter list in the descendants then it is not wise to use super. I'm going to publish the example below, I hope others can learn from it too.
Example (good):
class A(object): def f(self): print "A.f called"
class B(A): def f(self): super(B,self).f() print "B.f called"
class C(A): def f(self): super(C,self).f() print "C.f called"
class D(B,C): def f(self): super(D,self).f() print "D.f called"
d = D() d.f()
Results in:
A.f called C.f called B.f called D.f called
Example (bad):
class B(A): def f(self,what): super(B,self).f() print "B.f called (%s)" % what
Will result in:
C:/Python24/pythonw.exe -u "C:/Python/Projects/Test4/test4.py" Traceback (most recent call last): File "C:/Python/Projects/Test4/test4.py", line 22, in ? d.f() File "C:/Python/Projects/Test4/test4.py", line 17, in f super(D,self).f() TypeError: f() takes exactly 2 arguments (1 given)
Of course you cannot tell if super(C,self).f() will call A.f or not (when add another subclass under class A, it will change the MRO...)
If you do not want to add any other subclasses then probably you can use
super(C,self).f('foo')
but in that case it is equivalent to
A.f(self,'foo')
Best,
Laci 2.0
-- _________________________________________________________________ Laszlo Nagy web: http://designasign.biz IT Consultant mail: gand...@geochemsource.com
Laszlo Zsolt Nagy wrote: > I tested this and I realized that if you change the parameter list in > the descendants then it is not wise to use super. > I'm going to publish the example below, I hope others can learn from it > too.
> class A(object): > def f(self): > print "A.f called" > class B(A): > def f(self,what): > super(B,self).f() > print "B.f called (%s)" % what > class C(A): > def f(self): > super(C,self).f() > print "C.f called" > class D(B,C): > def f(self): > super(D,self).f() > print "D.f called"
> d = D() > d.f()
> Will result in:
> C:/Python24/pythonw.exe -u "C:/Python/Projects/Test4/test4.py" > Traceback (most recent call last): > File "C:/Python/Projects/Test4/test4.py", line 22, in ? > d.f() > File "C:/Python/Projects/Test4/test4.py", line 17, in f > super(D,self).f() > TypeError: f() takes exactly 2 arguments (1 given)
Yeah, this problem has been discussed before. It's a restriction of super that the method signature may not change the number of parameters in this way in the inheritance hierarchy.
The above is clearly a toy example. Do you really have a need for B to accept a parameter than none of the others accept? Makes it sounds like B.f might be better off as a different method. If the 'what' parameter is necessary for B.f, is it potentially applicable to the other f functions? Could you make 'what' a paramter in the other functions that defaults to, say, None?
One other possible (but IMHO somewhat ugly) solution:
The problem is that you need to know when you create A that some of the methods in the subclasses might change the signature. Or you need to do this to every method, which is kinda nasty.
Definitely take a moment to read Guido's comments on this issue: