Question
In Python, what functionality does the yield keyword provide?
For example, I am trying to understand this code:
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
And this is the caller:
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
What happens when the method _get_child_candidates is called?
- Is a list returned?
- Is a single element returned?
- Is the function called again?
- When do subsequent calls stop?
I want to understand exactly how yield behaves in this example and how the caller uses the returned value.
Short Answer
By the end of this page, you will understand that yield turns a Python function into a generator function. Calling it does not run the whole function immediately and does not return a normal list. Instead, it returns a generator object that produces values one at a time as iteration happens. You will also see how iteration pauses at each yield, resumes on the next request, and stops automatically when the function finishes.
Concept
In Python, yield is used to produce a value from a function without finishing the function permanently. Any function that contains yield becomes a generator function.
When you call a generator function:
- Python does not execute the whole function immediately.
- It returns a generator object.
- That generator can be iterated over with tools like:
forloopslist(...)next(...)- methods like
extend(...)
Each time iteration asks for a value:
- The function runs until it reaches
yield. - The yielded value is sent back to the caller.
- The function is paused, keeping its local variables and current position.
- On the next iteration request, the function resumes from where it paused.
- When the function reaches the end, iteration stops.
This matters because generators are:
- memory-efficient: they produce values one at a time instead of building a full list first
- lazy: values are computed only when needed
- useful for streaming or traversal: especially when walking trees, reading files, or processing large datasets
In your example, _get_child_candidates(...) does not return a list. It returns a generator that may yield:
Mental Model
Think of a generator like a bookmark inside a function.
A normal return is like closing the book and handing back the final result.
A yield is like saying:
"Here is one value for now. Keep my place open. I may have more later."
When the caller asks again, Python opens the book at the bookmark and continues reading until the next yield or the end.
For your tree example, _get_child_candidates(...) is like checking two doors:
- If the left door is valid, hand it out.
- Pause.
- If the right door is valid, hand it out.
- Pause.
- Then stop.
So instead of building a list of doors first, the function hands them out one by one as needed.
Syntax and Examples
The basic syntax is:
def my_generator():
yield 1
yield 2
yield 3
Calling it does not give you a list:
g = my_generator()
print(g)
Output will look like this:
<generator object my_generator at 0x...>
To get values, you iterate:
for value in my_generator():
print(value)
Output:
1
2
3
You can also convert it to a list:
print(list(my_generator()))
Output:
[1, , ]
Step by Step Execution
Consider this simplified version:
def get_items():
print("Start")
yield "A"
print("Middle")
yield "B"
print("End")
Now trace it:
g = get_items()
At this moment:
- the function body has not fully run
gis a generator object- nothing has been yielded yet
Next:
first = next(g)
What happens:
- The function starts running.
print("Start")runs.- It reaches
yield "A". "A"is returned to the caller.- The function pauses there.
Now:
first == "A"- the function has not finished
Real World Use Cases
Generators and yield are common in real Python programs when values should be produced gradually.
Tree and graph traversal
Your example is exactly this kind of use case.
- walking nodes in a tree
- finding neighboring nodes
- exploring paths lazily
Reading large files
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip()
This avoids loading the whole file into memory.
Processing API or database results
A program can yield records one by one instead of collecting everything first.
Data pipelines
Generators are often chained together:
def numbers():
for i in range(10):
yield i
def evens(values):
for v in values:
if v % == :
v
Real Codebase Usage
In real codebases, developers use generators for clarity, performance, and composability.
Lazy traversal
Instead of building temporary lists, code yields items as they are discovered.
def walk_nodes(node):
if node.left:
yield node.left
if node.right:
yield node.right
Filtering during iteration
A generator can apply conditions before yielding values.
def valid_users(users):
for user in users:
if user.is_active:
yield user
Validation and guard-style skipping
Generators often combine simple checks with early skipping.
def non_empty_lines(lines):
for line in lines:
line = line.strip()
if not line:
continue
yield line
Replacing temporary lists
Common Mistakes
Mistake 1: Thinking yield returns a list
Broken expectation:
def numbers():
yield 1
yield 2
x = numbers()
print(x[0])
This fails because x is a generator, not a list.
Use one of these instead:
x = list(numbers())
print(x[0])
or iterate:
for n in numbers():
print(n)
Mistake 2: Confusing yield with return
def example():
yield 1
yield 2
This produces multiple values over time.
But:
Comparisons
| Concept | What it does | Produces values | Memory behavior | Typical use |
|---|---|---|---|---|
return | Ends the function and sends back one final result | Once | Usually computes full result before returning | Simple functions |
yield | Pauses the function and sends back one value at a time | Multiple times | Lazy, often memory-efficient | Iteration, streaming, traversal |
| List | Stores all items immediately | All at once | Uses memory for all items | Small to medium collections, indexing |
| Generator | Produces items on demand | One at a time | Usually lower memory usage | Large data, pipelines, traversal |
Cheat Sheet
# A function with yield is a generator function
def gen():
yield 1
yield 2
Key rules
- Calling a generator function returns a generator object.
- The function body runs when iteration begins, not all at once.
- Each
yieldproduces one value and pauses the function. - Local state is preserved between yields.
- When the function ends, iteration stops with
StopIteration. - A generator can yield zero, one, or many values.
- Generators are iterable but are not lists.
- Once consumed, a generator is exhausted.
Common ways to consume a generator
g = gen()
next(g)
list(gen())
for x in gen():
print(x)
In your example
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
This means:
- call the generator function
- iterate over each yielded child
- append each child to
candidates
FAQ
Does yield return a list in Python?
No. It returns a generator object. You can convert it to a list with list(generator) if needed.
What happens when a function with yield is called?
It returns a generator object immediately. The function body is then executed step by step as values are requested.
Can a generator yield more than one value?
Yes. A generator can yield any number of values, including none.
When does a generator stop?
It stops when the function reaches the end or hits a return. Python signals this with StopIteration internally.
Why use yield instead of building a list?
Because it can be more memory-efficient and lets you compute values only when needed.
Can I loop over a generator more than once?
Not the same generator object after it has been exhausted. You usually need to create a new generator by calling the generator function again.
Is yield only for large datasets?
No. It is also useful for clean iteration logic, tree traversal, pipelines, and readable code.
Mini Project
Description
Build a small tree traversal helper that uses yield to return child nodes lazily. This mirrors the original question and helps you see how generator functions work in a realistic structure.
Goal
Create a function that yields available child nodes one at a time and then collect them using normal iteration.
Requirements
- Create a simple
Nodeclass with optionalleftandrightchildren. - Add a method that uses
yieldto produce existing child nodes. - Traverse a node and collect its children into a list.
- Show that the generator can yield zero, one, or two values.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Catch Multiple Exceptions in One except Block in Python
Learn how to catch multiple exceptions in one Python except block using tuples, with examples, mistakes, and real-world usage.
Convert Bytes to String in Python 3
Learn how to convert bytes to str in Python 3 using decode(), text mode, and proper encodings with practical examples.