0

I'm trying to get the top node of a chain in getTopParent(). When I print out self.name, it indeed prints out the name of the parent instance; however, when I return self, it returns None. Why is this?

class A:
    def __init__( self, name ):
        self.name = name
        self.owner = None
    def setChild( self, l ):
        l.owner = self
    def getTopParent( self ):
        if( self.owner == None ): # None == top parent
            print( "Got top: %s" % self.name )
            return self
        else:
            print( "checking %s" % self.name )
            self.owner.getTopParent()

a = A( "parent" )
b = A( "child1" )
c = A( "child2" )
d = A( "child3" )
a.setChild( b )
b.setChild( c )
c.setChild( d )

print( d.getTopParent() )
>>> checking child3
    checking child2
    checking child1
    Got top: parent
    None
idlackage
  • 2,715
  • 8
  • 31
  • 52

4 Answers4

7

You need to return self.owner.getTopParent()!

In Python, you must actually use the return statement to return something from a function, otherwise None is returned.

Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Thank you, it works! I'm a bit confused about the logic of this though: why can't it just return a value before the `else` instead of relying on where the program first runs from? (Eg. after the else statement). – idlackage Sep 04 '12 at 22:48
  • 1
    What happens is that the first function call makes a second function call. The *second* call returns `self`, but the first call needs to return the result of the second call rather than just drop the result on the floor. – Gabe Sep 04 '12 at 22:52
  • @Karl: Some languages like Perl don't require a `return` statement; they just return the last expression evaluated in a subroutine. Others, like many functional languages, don't even have a `return` statement. – Gabe Sep 05 '12 at 01:44
  • Sure, but there are plenty of other languages that do, which is the point. Anyway, even if the value is getting returned implicitly due to how the language works, that return-from-the-recursive-call is still a necessary part of how recursion works (just one that the programmer doesn't worry about). – Karl Knechtel Sep 06 '12 at 00:52
3

use return in

else:
    print( "checking %s" % self.name )
    return self.owner.getTopParent()

as None is the default return Value, then it'll output:

checking child3
checking child2
checking child1
Got top: parent
<__main__.A instance at 0xb77a628c>
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
2

I made some slight modifications to your code:

class A:
    def __init__( self, name ):
        self.name = name
        self.owner = None
    def setChild( self, l ):
        l.owner = self
    def getTopParent( self , depth=0):
        if( self.owner == None ): # None == top parent
            print( '  ' * depth + "Got top: %s" % self.name )
            print( '  ' * depth + "Returning: %s" % self)
            return self
        else:
            print( '  ' * depth + "checking %s" % self.name )
            tmp = self.owner.getTopParent(depth=depth + 1)
            print( '  ' * depth + "returned from call while checking %s" % self.name)
            print( '  ' * depth + "returned value was: %s" % tmp)


a = A( "parent" )
b = A( "child1" )
c = A( "child2" )
d = A( "child3" )
a.setChild( b )
b.setChild( c )
c.setChild( d )


d_top_parent = d.getTopParent()
print('----------------')
print d_top_parent

Now you can see what's going on when you call d.getTopParent(). Here's the output:

checking child3
  checking child2
    checking child1
      Got top: parent
      Returning: <__main__.A instance at 0x0000000002DE8DC8>
    returned from call while checking child1
    returned value was: <__main__.A instance at 0x0000000002DE8DC8>
  returned from call while checking child2
  returned value was: None
returned from call while checking child3
returned value was: None
----------------
None

In the middle there you can see it found the top parent and returned it. But return returns to the place where this function was called from[1]. So that only returns to the "checking child1" invocation.

Back in the "checking child1" invocation, it's called self.owner.getTopParent(), which has returned the top parent. In your original version, there was no more code after that, so the execution "dropped off the end" of the function. In my version, it stores the value that was returned from self.owner.getTopParent() into a variable tmp and prints it, so we can see what it was. But then it also drops off the end of the function.

In Python, hitting the end of a function is equivalent to return None, so that's what gets returned to the caller of "checking child1". So the "checking child2" invocation gets the value None returned from self.owner.getTopParent(). And then drops off the end of the function, returning None to its caller.[2] And so on.

If you add return tmp to my version, after the printing in the else branch, you get this output:

checking child3
  checking child2
    checking child1
      Got top: parent
      Returning: <__main__.A instance at 0x0000000002E68DC8>
    returned from call while checking child1
    returned value was: <__main__.A instance at 0x0000000002E68DC8>
  returned from call while checking child2
  returned value was: <__main__.A instance at 0x0000000002E68DC8>
returned from call while checking child3
returned value was: <__main__.A instance at 0x0000000002E68DC8>
----------------
<__main__.A instance at 0x0000000002E68DC8

Now each "checking childN" call is receives a value from calling self.owner.getTopParent() and returns it to the next-outermost caller.

If you call something, and you want its return value to go anywhere, you have to tell it where to go. That can be storing it somewhere with an assignment statement, or passing it directly as an argument to another call, or returning it directly. If you just have a bare call like this line:

self.owner.getTopParent()

Then the returned value goes nowhere and can't be used for any purpose[3].

The fact that the function you're calling happens to have the same name as the function you're in is irrelevant; recursive calls work exactly the same as non-recursive calls in every single way. I'm always surprised by how confusing recursion is for many people, but this is probably just because I don't remember what it was like to be learning it anymore. :)


[1] If you think about it, return has to work this way. How could you write a function that calls other functions if when they returned the return value jumped all the way to the very outer-most call (and appeared as output, if you're running in the interactive interpreter)?

[2] This None has nothing to do with receiving None from the call to self.owner.getTopParent().

[3] Unless you typed that directly in the interactive interpreter, in which case Python prints the return value for you so you can see what it was. It also secretly saves it in the variable _, so you can get at what it was if you decide you did need it after all after seeing it. But in principle, and in any other context, if you don't do anything with the return value of something you call then you lose the value.

Ben
  • 68,572
  • 20
  • 126
  • 174
1

Returning self worked just fine in the True side of the if. However, the recursive call in the else side does not return anything. Use return self.owner.getTopParent() instead.

D.Shawley
  • 58,213
  • 10
  • 98
  • 113