OOP: method overriding works in mysterious ways?

  • Thread starter John M. Gabriele
  • Start date
J

John M. Gabriele

Consider the following:

#!/usr/bin/python

#-----------------------------------------------------------------
class Grand_parent( object ):

def speak( self ):
print 'Grand_parent.speak()'
self.advise()

def advise( self ):
print 'Grand_parent.advise()'
self.critique()

def critique( self ):
print 'Grand_parent.critique()'


#-----------------------------------------------------------------
class Parent( Grand_parent ):

def speak( self ):
print '\tParent.speak()'
self.advise()

def advise( self ):
print '\tParent.advise()'
self.critique()

# ATM, the Parent is at a loss for words, and has no critique.


#-----------------------------------------------------------------
class Child( Parent ):

def speak( self ):
print '\t\tChild.speak()'
self.advise()

# Currently, the Child has no really useful advice to give.

def critique( self ):
print '\t\tChild.critique()'


#-----------------------------------------------------------------
print 'speak() calls advise(), then advise() calls critique().'
print

people = [ Grand_parent(), Parent(), Child() ]
for person in people:
person.speak()
print



====================
The output is:

speak() calls advise(), then advise() calls critique().

Grand_parent.speak()
Grand_parent.advise()
Grand_parent.critique()

Parent.speak()
Parent.advise()
Grand_parent.critique()

Child.speak()
Parent.advise()
Child.critique()


What's going on here with that last "Child.critique()"? The
Parent called self.critique(), and since it *had* no critique()
method, it should've deferred to it's parent's critique()
method, right? But instead, somehow Child.critique() got called.
Why?

---J
 
D

Dustan

Parent.critique() is calling self.critique(), which has been overriden
by Child.critique() instead of Parent.critique. It makes perfect sense
to me.
 
D

Dustan

Oh, I see what you mean. From my experience, the methods are passed
down, not referred to from the parent. That is, Parent does have its
own critique method, not a reference to Grand_parent.critique(). So
when Child calls self.advise, it is calling its inherrited copy. Then,
since the inherited Child.advise() from Parent.advise() calls
self.critique, it calls it's own overriden critique method.

I hope this makes sense.
 
?

=?iso-8859-1?B?QW5kcuk=?=

John said:
Consider the following:
[snip]

#-----------------------------------------------------------------
class Parent( Grand_parent ):

def speak( self ):
print '\tParent.speak()'
self.advise()

def advise( self ):
print '\tParent.advise()'
self.critique()

# ATM, the Parent is at a loss for words, and has no critique.


#-----------------------------------------------------------------
class Child( Parent ):

def speak( self ):
print '\t\tChild.speak()'
self.advise()

# Currently, the Child has no really useful advice to give.

def critique( self ):
print '\t\tChild.critique()'

Since Child has no advice() method, it inherits the one for Parent.
Thus, Child can be thought of as being defined as follows:

.. class Child( Parent ):
..
.. def speak( self ):
.. print '\t\tChild.speak()'
.. self.advise()
..
.. def advise( self ): # inherited from Parent
.. print '\tParent.advise()'
.. self.critique()
..
.. def critique( self ):
.. print '\t\tChild.critique()'
..
Note that "self" refer to the *instance* created, not the *class*.
print 'speak() calls advise(), then advise() calls critique().'
print

people = [ Grand_parent(), Parent(), Child() ]
for person in people:
person.speak() ### calls the instance's relevant method
print
[snip]

Child.speak()
Parent.advise() #inherited from Parent
Child.critique()

Now, does the output make sense?

André
 
D

Dustan

it calls it's own overriden critique method

(overriden meaning the one that did the overriding)
 
S

Scott David Daniels

Dustan said:
.... From my experience, the methods are passed
down, not referred to from the parent. That is, Parent does have its
own critique method, not a reference to Grand_parent.critique().
This is typical of static binding as (for example) seen in C++. If you
think of dynamically bound systems like Smalltalk, Ruby, and Python, the
more unusual case is wanting to go up the hierarchy (where you use super
to get the behavior you are apparently expecting). Think of Python's
method dispatch as always being "virtual".

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

John M. Gabriele

Scott said:
This is typical of static binding as (for example) seen in C++. If you
think of dynamically bound systems like Smalltalk, Ruby, and Python, the
more unusual case is wanting to go up the hierarchy (where you use super
to get the behavior you are apparently expecting). Think of Python's
method dispatch as always being "virtual".

Ah. Not only that, but the other ingredient (I think) is to keep in mind
that you're always calling instance methods via the self object -- and
that self object is always the same object regardless of how far up the
inheritance tree you go with those method calls, right?
--Scott David Daniels
(e-mail address removed)

---J
 
J

John M. Gabriele

Dustan said:
[snip] That is, Parent does have its
own critique method, not a reference to Grand_parent.critique().

Interesting. "It has its own" critique method? Hm. Not quite sure what
that means exactly...

Anyhow, I wasn't suggesting that Parent had a reference to
Grand_parent.critique(), only that Python would search *up* the tree
looking for the method it's looking to call.

Thanks!
 
J

John M. Gabriele

André said:
John M. Gabriele wrote:

Since Child has no advice() method, it inherits the one for Parent.
Thus, Child can be thought of as being defined as follows:

. class Child( Parent ):
.
. def speak( self ):
. print '\t\tChild.speak()'
. self.advise()
.
. def advise( self ): # inherited from Parent
. print '\tParent.advise()'
. self.critique()
.
. def critique( self ):
. print '\t\tChild.critique()'
.

That's a very interesting way to look at it... But I thought
that the Python interpreter takes care of walking up the
inheritance tree looking for the instance methods, rather
than what you've written above...
Note that "self" refer to the *instance* created, not the *class*.

Thanks. Right -- the self object always refers to the object that
you originally used to call the instance method (here, speak()).
Now, does the output make sense?

André

Bearing in mind what self is referring to, then yes,
the output does make sense. Thanks. :)

---J
 
M

Mike Meyer

John M. Gabriele said:
Consider the following:

#!/usr/bin/python

#-----------------------------------------------------------------
class Grand_parent( object ):

def speak( self ):
print 'Grand_parent.speak()'
self.advise()

def advise( self ):
print 'Grand_parent.advise()'
self.critique()

def critique( self ):
print 'Grand_parent.critique()'


#-----------------------------------------------------------------
class Parent( Grand_parent ):

def speak( self ):
print '\tParent.speak()'
self.advise()

def advise( self ):
print '\tParent.advise()'
self.critique()

# ATM, the Parent is at a loss for words, and has no critique.


#-----------------------------------------------------------------
class Child( Parent ):

def speak( self ):
print '\t\tChild.speak()'
self.advise()

# Currently, the Child has no really useful advice to give.

def critique( self ):
print '\t\tChild.critique()'


#-----------------------------------------------------------------
print 'speak() calls advise(), then advise() calls critique().'
print

people = [ Grand_parent(), Parent(), Child() ]
for person in people:
person.speak()
print



====================
The output is:

speak() calls advise(), then advise() calls critique().

Grand_parent.speak()
Grand_parent.advise()
Grand_parent.critique()

Parent.speak()
Parent.advise()
Grand_parent.critique()

Child.speak()
Parent.advise()
Child.critique()


What's going on here with that last "Child.critique()"? The
Parent called self.critique(), and since it *had* no critique()
method, it should've deferred to it's parent's critique()
method, right? But instead, somehow Child.critique() got called.
Why?

Because that's the way Python searchs for object attributes. Nobody
made it explicit, so I will. This is simplified, ignoring various
complications:

When looking up a value for self.foo, you first look for
attribute foo of self. You then check the class of self for
attribute foo. You then check the parent class of the last
class you checked for attribute foo. You repeat that last step
until there is no parent class.

Like I said, that's simplified. But it's sufficent to explain what
you're seeing.

<mike
 
C

Christophe

John M. Gabriele a écrit :
Consider the following:

#!/usr/bin/python

#-----------------------------------------------------------------
class Grand_parent( object ):

def speak( self ):
print 'Grand_parent.speak()'
self.advise()

def advise( self ):
print 'Grand_parent.advise()'
self.critique()

def critique( self ):
print 'Grand_parent.critique()'


#-----------------------------------------------------------------
class Parent( Grand_parent ):

def speak( self ):
print '\tParent.speak()'
self.advise()

def advise( self ):
print '\tParent.advise()'
self.critique()

# ATM, the Parent is at a loss for words, and has no critique.


#-----------------------------------------------------------------
class Child( Parent ):

def speak( self ):
print '\t\tChild.speak()'
self.advise()

# Currently, the Child has no really useful advice to give.

def critique( self ):
print '\t\tChild.critique()'


#-----------------------------------------------------------------
print 'speak() calls advise(), then advise() calls critique().'
print

people = [ Grand_parent(), Parent(), Child() ]
for person in people:
person.speak()
print



====================
The output is:

speak() calls advise(), then advise() calls critique().

Grand_parent.speak()
Grand_parent.advise()
Grand_parent.critique()

Parent.speak()
Parent.advise()
Grand_parent.critique()

Child.speak()
Parent.advise()
Child.critique()


What's going on here with that last "Child.critique()"? The
Parent called self.critique(), and since it *had* no critique()
method, it should've deferred to it's parent's critique()
method, right? But instead, somehow Child.critique() got called.
Why?

---J
Because that's the way virtual methods are supposed to work. When you
can a methon foo on an object, you always start your search from the
current object class and not the class of the current function.
 
C

Chris Mellon

John M. Gabriele said:
Consider the following:

#!/usr/bin/python

#-----------------------------------------------------------------
class Grand_parent( object ):

def speak( self ):
print 'Grand_parent.speak()'
self.advise()

def advise( self ):
print 'Grand_parent.advise()'
self.critique()

def critique( self ):
print 'Grand_parent.critique()'


#-----------------------------------------------------------------
class Parent( Grand_parent ):

def speak( self ):
print '\tParent.speak()'
self.advise()

def advise( self ):
print '\tParent.advise()'
self.critique()

# ATM, the Parent is at a loss for words, and has no critique.


#-----------------------------------------------------------------
class Child( Parent ):

def speak( self ):
print '\t\tChild.speak()'
self.advise()

# Currently, the Child has no really useful advice to give.

def critique( self ):
print '\t\tChild.critique()'


#-----------------------------------------------------------------
print 'speak() calls advise(), then advise() calls critique().'
print

people = [ Grand_parent(), Parent(), Child() ]
for person in people:
person.speak()
print



====================
The output is:

speak() calls advise(), then advise() calls critique().

Grand_parent.speak()
Grand_parent.advise()
Grand_parent.critique()

Parent.speak()
Parent.advise()
Grand_parent.critique()

Child.speak()
Parent.advise()
Child.critique()


What's going on here with that last "Child.critique()"? The
Parent called self.critique(), and since it *had* no critique()
method, it should've deferred to it's parent's critique()
method, right? But instead, somehow Child.critique() got called.
Why?

Because that's the way Python searchs for object attributes. Nobody
made it explicit, so I will. This is simplified, ignoring various
complications:

When looking up a value for self.foo, you first look for
attribute foo of self. You then check the class of self for
attribute foo. You then check the parent class of the last
class you checked for attribute foo. You repeat that last step
until there is no parent class.

Like I said, that's simplified. But it's sufficent to explain what
you're seeing.

If your interested in the non-simplified complicated rules for exactly
how methods are looked up (only different than the simple case when
you get into multiple inheritence), see
http://www.python.org/2.3/mro.html
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top