14. 生成器 — Python 进阶

生成器是可以在运行中暂停和恢复的函数,返回可以迭代的对象。 与列表不同,它们是懒惰的,因此一次仅在被询问时才产生一项。 因此,在处理大型数据集时,它们的内存效率更高。

生成器的定义类似于普通函数,但是使用 yield 语句而不是 return

def my_generator():
    yield 1
    yield 2
    yield 3

执行生成器函数

调用该函数不会执行它,而是函数返回一个生成器对象,该对象用于控制执行。 生成器对象在调用 next() 时执行。 首次调用 next() 时,执行从函数的开头开始,一直持续到第一个 yield 语句,在该语句中返回语句右边的值。 随后对 next() 的调用从 yield 语句继续(并循环),直到达到另一个 yield。 如果由于条件而未调用 yield 或到达末尾,则会引发 StopIteration 异常:

def countdown(num):
    print('Starting')
    while num > 0:
        yield num
        num -= 1

# 这不会打印 'Starting'
cd = countdown(3)

# 这会打印 'Starting' 以及第一个值
print(next(cd))

# 会打印第二个值
print(next(cd))
print(next(cd))

# 这会引发 StopIteration
print(next(cd))
    Starting
    3
    2
    1
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-1-3941498e0bf0> in <module>
         16 
         17 # this will raise a StopIteration
    ---> 18 print(next(cd))

    StopIteration:
# 你可以使用 for 循环来遍历一个生成器对象
cd = countdown(3)
for x in cd:
    print(x)
    Starting
    3
    2
    1
# 你可以将其用于接受可迭代对象作为输入的函数
cd = countdown(3)
sum_cd = sum(cd)
print(sum_cd)

cd = countdown(3)
sorted_cd = sorted(cd)
print(sorted_cd)
    Starting
    6
    Starting
    [1, 2, 3]

最大的优点:迭代器节省内存!

由于这些值是延迟生成的,即仅在需要时才生成,因此可以节省大量内存,尤其是在处理大数据时。 此外,我们不必等到所有元素生成后再开始使用它们。

# 如果没有生成器,则必须将完整序列存储在此处的列表中
def firstn(n):
    num, nums = 0, []
    while num < n:
        nums.append(num)
        num += 1
    return nums

sum_of_first_n = sum(firstn(1000000))
print(sum_of_first_n)
import sys
print(sys.getsizeof(firstn(1000000)), "bytes")
    499999500000
    8697464 bytes
# 使用生成器,不需要额外的序列来存储数字
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))
print(sum_of_first_n)
import sys
print(sys.getsizeof(firstn(1000000)), "bytes")
    499999500000
    120 bytes

另一个例子:斐波那契数列

def fibonacci(limit):
    a, b = 0, 1 # 前两个数
    while a < limit:
        yield a
        a, b = b, a + b

fib = fibonacci(30)
# 生成器对象可以被转为列表(这儿只是用来打印)
print(list(fib))
    [0, 1, 1, 2, 3, 5, 8, 13, 21]

生成器表达式

就像列表推导一样,生成器可以用相同的语法编写,除了用括号代替方括号。 注意不要混淆它们,因为由于函数调用的开销,生成器表达式通常比列表理解要慢(https://stackoverflow.com/questions/11964130/list-comprehension-vs-generator-expressions-weird-timeit-results/11964478#11964478)。

# 生成器表达式
mygenerator = (i for i in range(1000) if i % 2 == 0)
print(sys.getsizeof(mygenerator), "bytes")

# 列表推导式
mylist = [i for i in range(1000) if i % 2 == 0]
print(sys.getsizeof(mylist), "bytes")
    120 bytes
    4272 bytes

生成器背后的概念

这个类将生成器实现为可迭代的对象。 它必须实现 __iter____next__ 使其可迭代,跟踪当前状态(在这种情况下为当前数字),并注意 StopIteration。 它可以用来理解生成器背后的概念。 但是,有很多样板代码,其逻辑并不像使用 yield 关键字的简单函数那样清晰。

class firstn:
    def __init__(self, n):
        self.n = n
        self.num = 0
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.num < self.n:
            cur = self.num
            self.num += 1
            return cur
        else:
            raise StopIteration()
             
firstn_object = firstn(1000000)
print(sum(firstn_object))
    499999500000

GitHub repo: qiwihui/blog

Follow me: @qiwihui

Site: QIWIHUI

View on GitHub