杰瑞科技汇

yield return在Python中如何实现?

Of course! The Python equivalent of C#'s yield return is the yield keyword used in a function to create a generator.

yield return在Python中如何实现?-图1
(图片来源网络,侵删)

Let's break it down, starting with the simple concept and then building up to more advanced use cases.

The Core Idea: From Function to Generator

In a normal function, when you use the return statement, the function's execution stops, and a single value is sent back to the caller.

A generator function, defined with yield, is different. When it reaches a yield statement, it pauses its execution and "yields" a value back to the caller. Crucially, it remembers its state (the values of its local variables and the point in the code where it stopped). When the generator is asked for the next value, it resumes execution right where it left off.

This makes generators perfect for handling large sequences of data or streams of data, as they don't need to store the entire sequence in memory at once.

yield return在Python中如何实现?-图2
(图片来源网络,侵删)

Basic Example: The yield Keyword

Imagine you want to generate a sequence of numbers. A normal function would look like this:

# A normal function that returns a list
def generate_list(n):
    result = []
    for i in range(n):
        result.append(i)
    return result
# This creates the ENTIRE list in memory at once
my_list = generate_list(5)
print(my_list)
# Output: [0, 1, 2, 3, 4]

Now, let's convert this into a generator using yield:

# A generator function that yields numbers one by one
def generate_numbers(n):
    print("Generator started")
    for i in range(n):
        print(f"Yielding {i}")
        yield i  # Pause here and send out the value i
    print("Generator finished")
# This does NOT run the function immediately!
# It returns a generator object.
my_generator = generate_numbers(5)
print(f"Created generator object: {my_generator}")
print("-" * 20)
# You need to iterate over the generator to get the values
for number in my_generator:
    print(f"Received: {number}")

Output:

Created generator object: <generator object generate_numbers at 0x...>
--------------------
Generator started
Yielding 0
Received: 0
Yielding 1
Received: 1
Yielding 2
Received: 2
Yielding 3
Received: 3
Yielding 4
Received: 4
Generator finished

Key Observations:

yield return在Python中如何实现?-图3
(图片来源网络,侵删)
  1. generate_numbers(5) is not executed immediately. It just creates a "generator object".
  2. The for loop drives the generator. Each iteration calls the generator's internal __next__() method.
  3. Execution pauses at yield i. The value of i is sent out.
  4. When the for loop asks for the next value, the function resumes from the line after the yield.
  5. Once the function finishes (the for loop ends), the generator is considered exhausted and will raise StopIteration.

The yield from Statement (Python 3.3+)

This is the direct equivalent of C#'s yield return when you want to yield all items from another iterable (like another generator or a list).

It's a cleaner way to write nested loops for yielding.

Without yield from:

def sub_generator():
    yield 'A'
    yield 'B'
def main_generator():
    yield 1
    yield 2
    # The old way: manually loop and yield
    for item in sub_generator():
        yield item
    yield 3
for item in main_generator():
    print(item)
# Output: 1, 2, A, B, 3

With yield from:

This is much cleaner and more readable.

def sub_generator():
    yield 'A'
    yield 'B'
def main_generator():
    yield 1
    yield 2
    # The new, clean way!
    yield from sub_generator()
    yield 3
for item in main_generator():
    print(item)
# Output: 1, 2, A, B, 3

yield from essentially delegates the iteration to the sub-generator.


Sending Data Back: yield as an Expression

This is a more advanced feature that makes Python generators incredibly powerful. A yield statement can be used on the right-hand side of an assignment, allowing the caller to send data back into the generator.

This creates a two-way communication channel. The syntax is value = yield.

Example: A simple "co-routine" that processes data

def accumulator():
    print("Accumulator started")
    total = 0
    while True:
        # Yield pauses and waits for a value to be sent IN
        received = yield total
        if received is None:
            break
        total += received
        print(f"Added {received}, new total: {total}")
    print("Accumulator finished")
# 1. Create the generator
acc = accumulator()
# 2. Prime the generator: you must send None to start it
#    This advances execution to the `yield` statement.
next(acc) # or acc.send(None)
# 3. Now we can send values into it
print(acc.send(10)) # Output: Added 10, new total: 10
print(acc.send(5))  # Output: Added 5, new total: 15
# 4. To stop it, we can raise a GeneratorExit or send a special value
#    Here we send None to trigger the break condition.
acc.send(None) 
# Output: Accumulator finished

How it works:

  1. yield total does two things:
    • It sends the current value of total out to the caller.
    • It pauses the function's execution and waits for a value to be sent in.
  2. acc.send(10) sends the number 10 into the generator. This value becomes the result of the yield expression (received = 10).
  3. The generator resumes, processes the number, and loops back to the yield, ready for the next value.

Summary: yield vs. return

Feature return (Normal Function) yield (Generator Function)
Purpose Returns a single, final value. Produces a sequence of values over time.
Execution Function stops and is destroyed. Function pauses and saves its state.
Memory Returns the entire result at once. Yields one item at a time, saving memory.
Caller Gets a direct value. Gets a generator object that must be iterated.
Data Flow One-way: out of the function. Two-way: can receive data back via send().
C# Equivalent return yield return

Common Use Cases for yield

  1. Reading Large Files: Process a massive file line by line without loading it all into memory.

    def read_large_file(file_path):
        with open(file_path, 'r') as f:
            for line in f:
                yield line.strip()
    for line in read_large_file('huge_log_file.txt'):
        process(line)
  2. Generating Infinite Sequences: Create sequences that don't have a predefined end.

    def counter():
        i = 0
        while True:
            yield i
            i += 1
    count = counter()
    print(next(count)) # 0
    print(next(count)) # 1
  3. Implementing Custom Iterators: Make your own objects iterable.

    class Countdown:
        def __init__(self, start):
            self.start = start
        def __iter__(self):
            # This method makes the object iterable
            # It should return a generator
            n = self.start
            while n > 0:
                yield n
                n -= 1
    for num in Countdown(5):
        print(num)
    # Output: 5, 4, 3, 2, 1
分享:
扫描分享到社交APP
上一篇
下一篇