5

Playing around with trees, I stumbled over this behaviour:

def descendants (self):
    return #or "pass" or "42"

obviously returns None.

On the other hand side:

def descendants (self):
    return
    yield 42

returns a generator which yields nothing (actually the behaviour I needed for leaf nodes).

Could somebody explain to me what is happening under the hood here?

Shouldn't the yield 42 be unreachable code? (I guess the decision whether a function is a generator or a "normal" function is made at compile time, based on whether it contains one or various yield statements, be they reachable or not. But this is just a shot in the dark.)


The context is the following: I have trees and each node is either a tree or a leaf. Now I want to generate all the descendants of a node:

class Leaf (Node):
    @property
    def descendants (self):
        return
        yield 42

class Tree (Node):
    @property
    def descendants (self):
        for child in self.children:
            yield child
            yield from child.descendants
Hyperboreus
  • 31,997
  • 9
  • 47
  • 87

1 Answers1

5

As I understand it, the yield keyword inside a function is detected at compile-time. The result is that the function no longer behaves like a normal function. When a function with a yield keyword is called, the function IMMEDIATELY returns a lazy generator object which produces variables as needed according to the defined function. The code in your function is only run when the generator is iterated through.

It's more succinctly explained here.

So descendants is called, and since the yield keyword is present in the function, a generator object is immediately returned. Since descendants immediately returns, however, the generator yields no values-but it's definitely still a generator.

Community
  • 1
  • 1
jayelm
  • 7,236
  • 5
  • 43
  • 61
  • Thanks for the link. I found the linked question, but failed to scroll down to the answer you linked. Let me digest this a bit. – Hyperboreus Apr 10 '14 at 03:28
  • Do you have any links to language docs, as this doesn't say anything: https://docs.python.org/3.4/tutorial/classes.html#generators – Hyperboreus Apr 10 '14 at 03:38
  • @JesseMu +1 You've made clean and accurate explanation. – Raymond Hettinger Apr 10 '14 at 03:40
  • @Hyperboreus yeah, the docs don't make it very clear. I think it's implicit in the idea that the generator "resumes where it left off" once `next()` is called. – jayelm Apr 10 '14 at 03:45
  • @Hyperboreus see the generator object, explains slightly better https://docs.python.org/3.4/glossary.html#term-generator – jayelm Apr 10 '14 at 03:45
  • 1
    @RaymondHettinger +1 from me, too, but: Am I the only one here who thinks that it is somewhat strange that unreachable code makes a difference? At a minimum, it doesn't comply with the principle of least surprise... (same as: `def f(): print(x); return; x = x` while having an `x` in the enclosing scope). – Hyperboreus Apr 10 '14 at 03:48