[pythonvis] Slice of Py: Contacting Specific Classes and Methods

  • From: "Richard Dinger" <rrdinger@xxxxxxxxxx>
  • To: "pythonvis" <pythonvis@xxxxxxxxxxxxx>
  • Date: Sun, 22 Feb 2015 14:54:02 -0800

Contacting Your Ancestors 
This Slice will focus on how to contact your ancestors in the inheritance tree. 
Since a séance is not really practical, we are left with two approaches: 
1 Indirect calling using the super built-in type 
2 Direct calling with unbound method calls 
This Slice will provide an overview of using both methods and take a quick look 
under the hood as well. 
Super Overview and Limitations 
The purpose of the super built-in is to smoothly delegate method calls for 
classes in the ancestor tree of an instance. That works best if the classes are 
designed cooperatively, which means: 
1 All classes must be a subclass of object. 
2 The method being called by super must exist. 
3 The call signature for the caller and called method must be identical. 
4 Each occurrence of the method uses super, except the root class. 
First, take a look at a simple single inheritance (SI) class hierarchy where 
each class has a method named method1 that simply announces it is running and 
then uses either an unbound method or super to call the next ancestor's version 
of the method. The usage of unbound method or super is determined by the u and 
s flags set at the top of the code. The entire text of this Example 1 file and 
Example 2 are at the end of this Slice. 
Example 1: A Simple Inheritance Tree 
# begin code 
u = 0 # 1 = use, 0 = do not use unbound methods 
s = 0 # 1 = use, 0 = do not use super 
assert not(s and u), 'both flags cannot be True' 
class Top(object): 
def method1(self): 
print 'processing Top' 
class Middle(Top): 
def method1(self): 
print 'processing Middle' 
if s: super(Middle, self).method1() 
if u: Top.method1(self) 
class Bottom(Middle): 
def method1(self): 
print 'processing Bottom' 
if s: super(Bottom, self).method1() 
if u: Middle.method(self) 
# create an instance of Bottom and run method1 
b = Bottom() # make an instance 
b.method1() # run method1 
# suspend code 
Running the above file unchanged will only run method1 in Bottom so it prints: 
processing Bottom 
Changing the s flag to 1 uses the super calls and prints: 
processing Bottom 
processing Middle 
processing Top 
What Super Does 
Many programmers think that calling super(Bottom, self) returns the immediate 
parent or super class of Bottom, which in this case is Middle. But that is not 
correct, according to the documentation: 
"super(type[, object-or-type]) 
Return a proxy object that delegates method calls to a parent or sibling class 
of type. This is useful for accessing inherited methods that have been 
overridden in a class. The search order is same as that used by getattr() 
except that the type itself is skipped." 
That is quite a mouthful, so lets take a closer look. 
Comparing Super with the Actual Parent 
First lets take a look at what super actually returns by activating and running 
the following code in Example 1. Note to activate code that is 'commented out' 
by triple quotes, prepend a hash (#) symbol to both triple quotes. 
# resume code 
# Note: change both """ to #""" to activate this 
""" 
# compare super(Bottom, b) and parent of Bottom 
print 'super of Bottom is:' 
print super(Bottom, b) 
print 'parent of bottom is Middle' 
print Middle 
""" 
# end code 
Which prints something like: 
<super: <class 'Bottom'>, <Bottom object>> 
<class '__main__.Middle'> 
Well clearly super(Bottom, b) does not return Middle. As noted in the 
documentation super returns a proxy object and that is what that ,<super <class 
...>> thing is. 
What the Proxy Does 
The proxy determines the nearest ancestor of a class in an inheritance tree 
according to a linearization algorithm called the c3 algorithm. For single 
inheritance trees that turns out to be the obvious choice the parent. The proxy 
delegates the method call to that ancestor. 
A proxy object is needed because of Python's support of multiple inheritance 
(MI). If you have a class X that inherits from both A1 and A2 for example 
class X(A1, A2): 
What is the super class of X? Is it A1 or A2 (or both)? The proxy object 
determines the answer through that c3 algorithm, which uses the MRO (method 
Resolution Order)discussed in a previous Slice. MI trees will be discussed 
later in this Slice. 
Some Single Inheritance Examples 
In our example 1 code with s=1 and u=0, instance b calls method1 in Bottom, 
which prints its message. Bottom's method1 then uses super to call method1 in 
Middle, which prints its message. Middle's method1 then uses super to call 
method1 in Top, which prints its message. So the output is: 
processing Bottom 
processing Middle 
processing Top 
Change the s flag to 0 and the u flag to 1 and the output is the same, only the 
unbounded calls are not as indirect as super. Super lets you call a method 
without directly specifying a class name. This indirection can make adding a 
new class simpler since the called class is not specified. 
Now change the name of method1 in Middle and run it again twice, once with s=1 
and once with u=1. You now get: 
processing Bottom 
processing Top 
In this case after running method1 in Bottom, the proxy tries to call method1 
in Middle. Since Middle does not have a method1, the search continues up the 
tree to Top. Top has a method1, which is then called. 
That is probably what you want it to do. Since Top's version of method1 is not 
overridden by a method1 in Middle, the system uses the first match found above 
Middle in the inheritance tree. 
This is doing more than just trying to use the parent of Bottom. Instead the 
proxy returned by super searches for a valid method1 in the tree of classes 
starting with the parent of Bottom. Note that using an unbound method call to 
Middle has the same effect. Using either super or unbound method calls have the 
same effect: They both initiate an MRO search and the first occurrence of 
method1 found is called. 
Example 2 A Multiple Inheritance Tree 
Our second example is a tree with a closed diamond shape. There is a Top class 
that inherits object. Then a Left class and a Right class below and to the left 
and right both inheriting Top. And finally the Bottom class below and in the 
center inheriting from Left and Right. To start, we are using all unbound 
method calls. 
# begin code 
u = 1 # 1 = use, 0 = do not use unbound methods 
s = 0 # 1 = use, 0 = do not use super 
assert not(s and u), 'both flags cannot be True' 
class Top(object): 
def method1(self): 
print 'processing Top' 
class Left(Top): 
def method1(self): 
print 'processing Left' 
if s: super(Left, self).method1() 
if u: Top.method1(self) 
class Right(Top): 
def method1(self): 
print 'processing Right' 
if s: super(Right, self).method1() 
if u: Top.method1(self) 
class Bottom(Left, Right): 
def method1(self): 
print 'processing Bottom' 
if s: super(Bottom, self).method1() 
if u: Left.method1(self) 
if u: Right.method1(self) 
# create an instance and run method1 
b = Bottom() # make an instance 
b.method1() # run method1 
# end code 
The Problem with MI Trees 
When you run Example 2 you get: 
processing Bottom 
processing Left 
processing Top 
processing Right 
processing Top 
Now top runs twice! That could cause some problems. You may not think this 
diamond inheritance tree very likely in your code, but with the requirement 
that all classes either inherit object directly or inherit from a descendent of 
object, makes almost any MI tree a diamond. 
This is just the sort of problem that super was built to solve. Change the 
flags in Example 2 to u=0 and s=1 and run it again, you should get: 
processing Bottom 
processing Left 
processing Right 
processing Top 
The problem is solved for cooperative classes that are designed to work 
together using super. 
For now I recommend that you stick to single inheritance designs and avoid all 
the problems associated with multiple inheritance. 
Contacting Ancestors 
You can call any ancestor's method if it is visible by using an unbound method 
calls such as: 
inside a class 
ClassName.methodName(self, args) 
outside a class with an instance x 
ClassName.methodName(x, args) 
And you use super inside a class like this- 
super(ClassName, self).methodName(args)

The code for Example 1 and 2 follow (2 spaces each level of indent.

Example 1
# begin code
u = 0 # 1 = use, 0 = do not use unbound methods
s = 0 # 1 = use, 0 = do not use super
assert not(s and u), 'both flags cannot be True'

class Top(object):
  def method1(self):
    print 'processing Top'

class Middle(Top):
  def method1(self):
    print 'processing Middle'
    if s: super(Middle, self).method1()
    if u: Top.method1(self)

class Bottom(Middle):
  def method1(self):
    print 'processing Bottom'
    if s: super(Bottom, self).method1()
    if u: Middle.method(self)

# ccreate an instance of Bottom and run method1
b = Bottom()  # make an instance
b.method1()  # run method1
# suspend code

# resume code
# Note: change both """ to #""" to activate this
"""
# compare super(Bottom,  b) and parent of Bottom
print 'super of Bottom is:'
print super(Bottom, b)
print 'parent of bottom is Middle'
print Middle
"""
# end code

Example 2

# begin code
u = 1 # 1 = use, 0 = do not use unbound methods
s = 0 # 1 = use, 0 = do not use super
assert not(s and u), 'both flags cannot be True'


class Top(object):
  def method1(self):
    print 'processing Top'

class Left(Top):
  def method1(self):
    print 'processing Left'
    if s: super(Left, self).method1()
    if u: Top.method1(self)

class Right(Top):
  def method1(self):
    print 'processing Right'
    if s: super(Right, self).method1()
    if u: Top.method1(self)

class Bottom(Left, Right):
  def method1(self):
    print 'processing Bottom'
    if s: super(Bottom, self).method1()
    if u: Left.method1(self)
    if u: Right.method1(self)
    
# ccreate an instance and run method1
b = Bottom()  # make an instance
b.method1()  # run method1
# end code

Other related posts:

  • » [pythonvis] Slice of Py: Contacting Specific Classes and Methods - Richard Dinger