super() and automatic method combination

P

Paul Rubin

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).
 
L

Laszlo Zsolt Nagy

I have the impression that this is supposed to call the f method
in both A and B, so it should print
Not really true. The first parameter of 'super' should be a type, not an
instance.
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.
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: (e-mail address removed)

Python forever!
 
D

Duncan Booth

Paul said:
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): Typo? 'c' should be 'C' in:
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.

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.
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.

You have to terminate the chain somehow. e.g.

class MyBase(object):
def f(self):
print "MyBase"

class A(MyBase):
def f(self):
super(A, self).f()
print "A"
...
class B(MyBase):
def f(self):
super(B, self).f()
print "B"
...
Anyway, is there a preferred way of writing this example so that C.f
automatically calls both A.f and B.f?

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.
 
L

Laszlo Zsolt Nagy

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: (e-mail address removed)

Python forever!
 
M

Michele Simionato

Paul Rubin:
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:

http://www.reportlab.org/~andy/accu2005/pyuk2005_simionato_wondersofpython.zip

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).

Michele Simionato
 
D

Duncan Booth

Laszlo said:
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:

super(AB, self).f() --> calls A.f()
super(A, self).f() --> calls B.f()
super(B, self).f() --> calls Base.f()

but in an instance of A:

super(A, self).f() --> calls Base.f()

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).
 
S

Scott David Daniels

Laszlo said:
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)
The difference is when you have a diamond inheritance diagram.
Here is a simple example:

class Bottom(object):
def f(self):
print 'Bottom'

class A(Bottom):
def f(self):
print 'A',
super(A, self).f()

class B(Bottom):
def f(self):
print 'B',
super(B, self).f()

class C(A, B):
def f(self):
print 'C',
super(C, self).f()

C().f()

C A B Bottom

-------
Versus:
-------

class Bottom(object):
def f(self):
print 'Bottom'

class A(Bottom):
def f(self):
print 'A',
Bottom.f(self)

class B(Bottom):
def f(self):
print 'B',
Bottom.f(self)

class C(A, B):
def f(self):
print 'C',
A.f(self)
B.f(self)

C().f()

C A Bottom
B Bottom

--Scott David Daniels
(e-mail address removed)
 
S

Steven Bethard

Paul said:
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)

You want a super object that doesn't raise an exception if the
superclass doesn't have a particular function. Try sopmething like:

py> class mysuper(super):
.... def __getattribute__(self, name):
.... try:
.... return super(mysuper, self).__getattribute__(name)
.... except AttributeError:
.... def null(*args, **kwargs):
.... pass
.... return null
....
py> class A(object):
.... def f(self):
.... mysuper(A, self).f()
.... print 'A'
....
py> class B(object):
.... def f(self):
.... mysuper(B, self).f()
.... print 'B'
....
py> class C(A, B):
.... def f(self):
.... mysuper(C, self).f()
.... print 'C'
....
py> C().f()
B
A
C

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.

STeVe
 
L

Laszlo Zsolt Nagy

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: (e-mail address removed)

Python forever!
 
S

Steven Bethard

Laszlo said:
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.
[snip and fixed formatting]

Example (bad):

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:

py> class A(object):
.... def f(self, *args, **kwargs):
.... print 'A.f'
....
py> class B(A):
.... def f(self, what, *args, **kwargs):
.... super(B, self).f(what, *args, **kwargs)
.... print 'B.f', what
....
py> class C(A):
.... def f(self, *args, **kwargs):
.... super(C, self).f(*args, **kwargs)
.... print 'C.f'
....
py> class D(B, C):
.... def f(self, what, *args, **kwargs):
.... super(D, self).f(what, *args, **kwargs)
.... print 'D.f', what
....
py> d = D()
py> d.f(42)
A.f
C.f
B.f 42
D.f 42
py> d.f(what=13)
A.f
C.f
B.f 13
D.f 13

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:

http://mail.python.org/pipermail/python-dev/2005-January/050656.html

The main point is that by adding parameters to functions in a subclass,
you violate the Liskov Substitutability Principle.

STeVe
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,276
Latest member
Sawatmakal

Latest Threads

Top