杰瑞科技汇

docs python yield

Of course! Here is a comprehensive explanation of Python's yield keyword, complete with analogies, code examples, and a comparison to return.

The Short & Sweet Answer

In Python, yield turns a function into a generator. Instead of returning a single value and exiting, a function with yield produces a sequence of values, pausing its execution each time it yields a value and resuming where it left off when asked for the next one.


The Core Concept: Generators and Lazy Evaluation

To understand yield, you first need to understand generators.

Think of a regular function like a recipe that you follow from start to finish, and at the end, you get one final dish (the return value).

A generator function with yield is like a vending machine. You press a button (next()), it gives you one item (a yielded value), and it remembers its place inside the machine. When you press the button again, it gives you the next item without having to reset the entire machine.

This "on-demand" production of values is called lazy evaluation. It's incredibly memory-efficient, especially for large or infinite sequences.


How yield Works: A Step-by-Step Breakdown

Let's look at a simple example.

def simple_generator():
    print("Generator started")
    yield 1
    print("Yielded 1, now continuing")
    yield 2
    print("Yielded 2, now ending")
    yield 3

Now, let's use it:

# 1. Calling the function does NOT run its code.
#    It returns a generator object.
gen = simple_generator()
print(f"Type of gen: {type(gen)}")
# Output: Type of gen: <class 'generator'>
# 2. The first call to next() starts the function.
#    It runs until it hits the first `yield`.
value1 = next(gen)
print(f"Got value: {value1}")
# Output:
# Generator started
# Got value: 1
# 3. The second call to next() resumes the function
#    right after the first `yield`.
value2 = next(gen)
print(f"Got value: {value2}")
# Output:
# Yielded 1, now continuing
# Got value: 2
# 4. The third call resumes after the second `yield`.
value3 = next(gen)
print(f"Got value: {value3}")
# Output:
# Yielded 2, now ending
# Got value: 3
# 5. If we call next() again, the function has finished.
#    It raises a StopIteration exception.
try:
    next(gen)
except StopIteration as e:
    print("Generator is exhausted, it has no more values.")
# Output:
# Generator is exhausted, it has no more values.

Key Takeaway: The function's state (local variables, instruction pointer) is "frozen" between yield statements.


yield vs. return: A Critical Comparison

This is the most important distinction to make.

Feature return yield
Purpose Exits the function and sends back a single, final value. Pauses the function and sends back a value. Can be called multiple times.
Function Type Creates a regular function. Turns the function into a generator.
Execution Function terminates after return. Function's state is saved. It resumes after the yield on the next call.
Value(s) Returns one value. Produces a sequence of values, one at a time.
Memory Returns the entire result at once. Generates values on-the-fly, saving memory.

Analogy:

  • return is like a chef who prepares one meal, serves it, and then goes home for the day.
  • yield is like a chef who prepares one dish, serves it, cleans up the station, and waits for the next order before preparing the next dish.

Practical Example 1: Generating a Sequence of Numbers

Let's create a generator for the Fibonacci sequence. This is a perfect use case because the sequence is infinite, and a generator can handle it without running out of memory.

def fibonacci_generator(n):
    """Generates the first n Fibonacci numbers."""
    a, b = 0, 1
    count = 0
    while count < n:
        yield a  # Yield the current Fibonacci number
        a, b = b, a + b  # Update the numbers for the next iteration
        count += 1
# How to use it
fib_gen = fibonacci_generator(10)
for number in fib_gen:
    print(number, end=" ")
# Output:
# 0 1 1 2 3 5 8 13 21 34

Notice how the for loop automatically handles the StopIteration exception for us. This is the idiomatic way to consume a generator.


Practical Example 2: Reading a Large File Line-by-Line

Imagine you have a multi-gigabyte log file. Reading it all into memory with file.read() would crash your program.

A generator is the perfect solution. It reads one line at a time, yields it, and then discards it, keeping memory usage low.

def read_large_file(file_path):
    """A generator to read a large file line by line."""
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip() # Yield one line at a time
# Usage (assuming 'huge_log.txt' exists)
# for line in read_large_file('huge_log.txt'):
#     process(line) # Process each line one by one

Advanced: The yield Expression (with send())

You can also use yield as an expression, which means it can receive a value. This is done using the .send() method on the generator.

The syntax is value = yield. The value passed to .send() becomes the result of the yield expression.

This creates a two-way communication channel, making the generator act like a coroutine.

def simple_coroutine():
    print("Coroutine started")
    # The first call must be next() or send(None)
    # because there's no yield to receive a value yet.
    received_value = yield "I am ready to receive a value."
    print(f"Coroutine received: {received_value}")
    yield "Thank you for the value."
# Create the coroutine
coro = simple_coroutine()
# Prime the coroutine to get to the first yield
message = next(coro)
print(f"Coroutine says: {message}")
# Output:
# Coroutine started
# Coroutine says: I am ready to receive a value.
# Now, send a value to the coroutine
response = coro.send("Hello from the outside world!")
print(f"Coroutine replies: {response}")
# Output:
# Coroutine received: Hello from the outside world.
# Coroutine replies: Thank you for the value.

This advanced feature is less common in everyday scripting but is fundamental in asynchronous programming (e.g., with asyncio) and for creating data pipelines.


Summary: When to Use yield

Use yield when you need to:

  1. Generate a large or infinite sequence of values and you don't want to store them all in memory at once.
  2. Create memory-efficient iterators for custom data structures.
  3. Stream data from a source (like a file or network) one piece at a time.
  4. Write coroutines for tasks that require a back-and-forth communication pattern (more advanced).

For everything else—when you just need to compute and return a single, final result—stick with a regular function and return.

分享:
扫描分享到社交APP
上一篇
下一篇