[译]python 产生器 (Generator)


简单入门

​ 迭代器(iterator)看作指向容器的一个指针,例如遍历一个列表里面的所有元素. 迭代器是一个抽象的工具.程序员可以用来访问容器(例如 集合set,列表list等等)里面的所有元素,而不用知道容器的结构,其他细节. 在一些面向对象编程的语言(如Java,Perl,Python),迭代器是在foreach循环里隐含使用的. 对应Python的for循环. 本文来源:Seakee.top. 未经允许不得转载.
产生器是一个用来实现或者生成迭器的特殊函数. 迭代器是Python里面一个基础的概念.大多数情况,都是隐式使用的,例如在for循环里面.
下面的例子展示了这个用法,例子遍历了一个列表(list),但是不要弄混,列表不是的一个迭代器,但是可以像迭代器一样使用.

>>> cities = ["Paris", "Berlin", "Hamburg", "Frankfurt", "London", "Vienna", "Amsterdam", "Den Haag"]
>>> for location in cities:
...     print("location: " + location)
... 
location: Paris
location: Berlin
location: Hamburg
location: Frankfurt
location: London
location: Vienna
location: Amsterdam
location: Den Haag
>>>

​ 实际上发生了什么呢,当你在for循环里面遍历字符串(string),列表(list),元组(tuple)时, 函数iter会调用在被遍历的对象上.iter函数的返回值就是一个迭代器, 我们就可以对这个迭代器调用next函数,直到这个迭代器耗尽,返回StopIteration异常:

>>> expertises = ["Novice", "Beginner", "Intermediate", "Proficient", "Experienced", "Advanced"]
>>> expertises_iterator = iter(expertises)
>>> next(expertises_iterator)
'Novice'
>>> next(expertises_iterator)
'Beginner'
>>> next(expertises_iterator)
'Intermediate'
>>> next(expertises_iterator)
'Proficient'
>>> next(expertises_iterator)
'Experienced'
>>> next(expertises_iterator)
'Advanced'
>>> next(expertises_iterator)
Traceback (most recent call last):
File "", line 1, in 
    StopIteration

​ 本质上,for循环也是通过调用next函数,在返回StopIteration时结束.  下面用while循环模拟for循环,注意,在代码中不要忘了捕获”Stop Iteration” 异常:本文来源:Seakee.top. 未经允许不得转载.

other_cities = ["Strasbourg", "Freiburg", "Stuttgart", 
"Vienna / Wien", "Hannover", "Berlin", 
"Zurich"]
city_iterator = iter(other_cities)
while city_iterator:
try:
    city = next(city_iterator)
    print(city)
except StopIteration:
    break

输出结果如下:

Strasbourg
Freiburg
Stuttgart
Vienna / Wien
Hannover
Berlin
Zurich

连续值类型和标准库里面主要类都支持迭代.字典(dict)类型也支持迭代.下面例子展示了对字典key的迭代

>>> capitals = { "France":"Paris", "Netherlands":"Amsterdam", "Germany":"Berlin", "Switzerland":"Bern", "Austria":"Vienna"}
>>> for country in capitals:
...     print("The capital city of " + country + " is " + capitals[country])
... 
The capital city of Switzerland is Bern
The capital city of Netherlands is Amsterdam
The capital city of Germany is Berlin
The capital city of France is Paris
>>>

主题无关:Some readers may be confused to learn from our example that the capital of the Netherlands is not Den Haag (The Hague) but Amsterdam. Amsterdam is the capital of the Netherlands according to the constitution, even though the Dutch parliament and the Dutch government are situated in The Hague, as well as the Supreme Court and the Council of State.

产生器(Generators)

​ 表面上看python生成器就像函数,但是他们在语法和语义上都有不同. 一个明显的区别就是yield语句.yield语句让一个函数变成生成器. 生成器是是一个能产成生成器对象的函数. 生成器对象像是一个能产生一系列数据的的函数,而不是只有一个返回值. 这一系列数值通过遍历这个生成器对象获得. 例如在for循环里面,这些值使用yield语句创建的数值将会被遍历 数值的创建是通过yield value这样的方式创建. 当代码运行到yield语句时,会停止.yield后面的值将会返回.这段代码运行会中断. 当next函数调用在这个生成器对象时,代码马上运行yield语句后面的代码. 在next调用之后,代码是接着调用之前状态下的运行, 这就意味着所有的局部变量是依然存在的,因为他们是自动保存的在两次next()调用之间 这是他和函数的根本区别,函数总是从函数体开始的地方运行,不管上一次调用是什么结果, 不会有任何静态(static)或者持久(persistent)的值. 在产生器代码里面可以有多个yield语句,在循环体里面也可以有yield语句. 如果生成器代码里买你有return语句,代码运行到这时将会停止,并触发StopIteration 异常. 生成(generator)这个名词有时候是有歧义的,既可以表示生成器函数,也可以表示由函数产生的生成器对象,生成器所有功能都可以通过基于迭代器的类来实现,生成器的关键优势是他们自动创建iter()方法和next()函数, 生成器提供一种简洁的方式便利方式生成大量或者无尽数据 下面是一个简单生成器例子,可以生成一系列城市名称

def city_generator():
    yield("London")
    yield("Hamburg")
    yield("Konstanz")
    yield("Amsterdam")
    yield("Berlin")
    yield("Zurich")
    yield("Schaffhausen")
    yield("Stuttgart")

可以用这个生成器产生生成器对象,生成所有的城市名

>>> from city_generator import city_generator
>>> city = city_generator()
>>> print(next(city))
London
>>> print(next(city))
Hamburg
>>> print(next(city))
Konstanz
>>> print(next(city))
Amsterdam
>>> print(next(city))
Berlin
>>> print(next(city))
Zurich
>>> print(next(city))
Schaffhausen
>>> print(next(city))
Stuttgart
>>> print(next(city))
Traceback (most recent call last):
    File "", line 1, in 
StopIteration

​ 就像看到的,我们创建了一个生成器对象,每一次调用next()返回一个城市. 在最后一个城市Stuttgart之后,再次调用next()时产生一个StopIteration expertises_iterator  能不能重置生成器,重新遍历,是一个常见的问题,答案是不可以. 但是我们可以重新生成产生器对象, 重新这样x = city_generator()的语句  虽然第一眼感觉yield语句和return语句很像,我们可以从上述例子看到大不同. 在上述例子中如果我们使用return语句,那么就是一个函数了.总会返回”London”而没有其他城市

运行机制(Method of Operation)

上面我们仔细介绍了生成器.生成器提供了了一个舒适的方法来生成迭代器,也称之为生成器运行机制

  • 生成器像函数一样调用,返回一个迭代器(iterator) 或者说一个生成器对象.生成器的的代码在这个时候不会运行.

  • next()函数将会调用在迭代器上,这个时候生产器开始像个函数一样运行.从第一行代码开始运行 代码运行到yield语句停止

  • yield返回yield后面表达式的值.这也像个函数,但是python记录yield运行的位置,和局部变量,为下一次调用做准备 当再次调用时,程序接着yield后面的语句运行,变量和上一次调用时一样

  • 当生成器单代码运行完,或者遇到return语句后面没有值时迭代器也就完成了

我们将用下面这个例子展示上述行为.下面定一个产生器来生成Fibonacci数列.  产生的数列是这样的:0,1,1,2,3,5,8,18  数学上是这样定义的:Fn = Fn – 1 + Fn – 2 ,初始值:F0 = 0 and F1 = 1

def fibonacci(n):
    """ A generator for creating the Fibonacci numbers """
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(5)
for x in f:
    print(x, " ", end="") # 
print()

上述产生式用来生成Fibonacci数列,用空格隔开  下面这个例子展示一个无穷的Fibonacci数列生成器,我在使用这样迭代器是一定要格外小心,控制迭代的终止

def fibonacci():
    """Generates an infinite sequence of Fibonacci numbers on demand"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

f = fibonacci()

counter = 0
for x in f:
    print(x, " ", end="")
    counter += 1
    if (counter > 10): 
        break 
print()

在产生器使用return

​ 自从python3.3,产生器也可以使用return语句了. 但是产生器里之中至少有一个yield语句才是产生器,在产生器里面return语句相当与抛出StopIteration异常.  让我们看一下StopIteration是怎么发生的.

>>> def gen():
...     yield 1
...     raise StopIteration(42)
... 
>>> 
>>> g = gen()
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in gen
StopIteration: 42
>>> 

例子说明return和raise StopIteration相同,或者近乎相同如果我们忽略异常栈

>>> def gen():
...     yield 1
...     return 42
... 
>>> 
>>> g = gen()
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration: 42
>>> 

send 方法和协同程序(Coroutines)

​ 一个产生器不光能发出对象,也可以接受对象,发送一个消息(例如 一个对象)给产生器, 可以通过调用send方法在这个产生器上.注意:send不光发送一个值给产生器同时也返回一个yield值. 下面例子将展示这种特性,通过这个简单的协同程序的例子

>>> def simple_coroutine():
...     print("coroutine has been started!")
...     x = yield 
...     print("coroutine received: ", x)
... 
>>> cr = simple_coroutine()
>>> cr

>>> next(cr)
coroutine has been started!
>>> cr.send("Hi")
coroutine received:  Hi
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>>

开始时一定要调用next()函数,因为产生需要开始运行.调用send方法在一个没有开始运行的产生器上将会导致异常

>>> cr = simple_coroutine()
>>> cr.send("Hi")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can't send non-None value to a just-started generator
>>>

​ 能调用森的方法的产生器必须在yield语句处等待.这样发送的数据才能被处理,才能赋值给变量. 我们还没有说的是,send函数能同时发送值和接收值,不过它只会发送None值. 发送给产生器的数据在产生器里面赋值给其他变量.在下面例子中,这个变量叫message. 我们的调用infinit_looper产生器,它传入一个序列参数,创建一个迭代器, 可无穷遍历这个序列参数,当遍历玩最后一个元素时,有从头开始. 通过发送一个index给产生器,我们遍历任意位置元素

def infinite_looper(objects):
    count = 0
    while True:
        if count >= len(objects):
            count = 0
        message = yield objects[count]
        if message != None:
            count = 0 if message < 0 else message
        else:
            count += 1 

下面展示如何使用上面产生器、

>>> x = infinite_looper("A string with some words")
>>> next(x)
'A'
>>> x.send(9)
'w'
>>> x.send(10)
'i'
>>>

throw 方法

throw()方法会在产生器暂停的地方触发一个异常,并且返回产生器下一个值. 产生器可以捕获到这个异常,如果不捕获,就会留给下一次调用. 上一个infinite_looper持续数列的值,但是我们不知当前index值,我们可以有通过throw方法获取到这个些信息 我们在产生器内捕获异常,并打印这个值:

def infinite_looper(objects):
    count = 0
    while True:
        if count >= len(objects):
            count = 0
        try:
            message = yield objects[count]
        except Exception:
            print("index: " + str(count))
        if message != None:
            count = 0 if message < 0 else message
        else:
            count += 1

像下面这样使用

>>> from generator_throw import infinite_looper
>>> looper = infinite_looper("Python")
>>> next(looper)
'P'
>>> next(looper)
'y'
>>> looper.throw(Exception)
index: 1
't'
>>> next(looper)
'h'

我们优化上一个例子,我们定义了SateOfGenerator异常

class StateOfGenerator(Exception):
     def __init__(self, message=None):
         self.message = message

def infinite_looper(objects):
    count = 0
    while True:
        if count >= len(objects):
            count = 0
        try:
            message = yield objects[count]
        except StateOfGenerator:
            print("index: " + str(count))
        if message != None:
            count = 0 if message < 0 else message
        else:
            count += 1

像这样使用它

>>> from generator_throw import infinite_looper, StateOfGenerator
>>> looper = infinite_looper("Python")
>>> next(looper)
'P'
>>> next(looper)
'y'
>>> looper.throw(StateOfGenerator)
index: 1
't'
>>> next(looper)
'h'
>>>

修饰产生器

还有一个问题,在产生器开始开始的时候无法指定序号.在做这个之前, 我们可以定义一个高级函数来启动产生器,同通过在创建的时候就调用他, 使产生器一开始就可以使用send方法

from functools import wraps

def get_ready(gen):
    """
    Decorator: gets a generator gen ready 
    by advancing to first yield statement
    """
    @wraps(gen)
    def generator(*args,**kwargs):   
        g = gen(*args,**kwargs)   
        next(g)   
        return g   
    return generator

@get_ready
def infinite_looper(objects):
    count = -1
    message = yield None
    while True:
        count += 1
        if message != None:
            count = 0 if message < 0 else message
        if count >= len(objects):
            count = 0
        message = yield objects[count]


x = infinite_looper("abcdef") 
print(next(x))
print(x.send(4))
print(next(x))
print(next(x))
print(x.send(5))
print(next(x))

程序会输出如下

a
e
f
a
f
a

你可能注意到ifinite_generator 也有一些改动

yield from

从python3.3开始可以使用yield from
yield from 语句可以在产生器里面使用
必须是可以迭代的,从这个里面提取值.
这个迭代器运行到触发StopIteration 为止.
这个迭代产生数据,从产生器调用着获取数据,如果有yield from 语句
下面例子,可以看到yield from和for 循环的异同

def gen1():
    for char in "Python":
        yield char
    for i in range(5):
        yield i

def gen2():
    yield from "Python"
    yield from range(5)

g1 = gen1()
g2 = gen2()
print("g1: ", end=", ")
for x in g1:
    print(x, end=", ")
print("\ng2: ", end=", ")
for x in g2:
    print(x, end=", ")
print()  

我们可以看到二者的输出是相同的

g1: , P, y, t, h, o, n, 0, 1, 2, 3, 4, 
g2: , P, y, t, h, o, n, 0, 1, 2, 3, 4,

yield from的一个好处就是可以将一个产生器分成多个产成器, 下面这个例子展示更加清晰

def cities():
    for city in ["Berlin", "Hamburg", "Munich", "Freiburg"]:
        yield city

def squares():
    for number in range(10):
        yield number ** 2

def generator_all_in_one():
    for city in cities():
        yield city
    for number in squares():
        yield number

def generator_splitted():
    yield from cities()
    yield from squares()

lst1 = [el for el in generator_all_in_one()]
lst2 = [el for el in generator_splitted()]
print(lst1 == lst2)

结果输出True,因为generator_all_in_one和generator_splitted产生的元素是相同的. 这就意味着yield from和yield 所有值是相同的. 同时,子产生器允许有return 一个值,且就是yield from表达式的值. 下面代码做一个简单的展示

def subgenerator():
    yield 1
    return 42

def delegating_generator():
    x = yield from subgenerator()
    print(x)

for x in delegating_generator():
    print(x)

结果如下

1
42

PEP 380

描述了yield from的完整语义.

原文还有两节 递归产生器(Recursive Generator)和产生器的产生器(A Generator of Generator)
还有练习题和答案
有兴趣的同学请自行参阅,或者反馈给我

原文地址 Generators

未经允许禁止转载!!!!

2 对 “[译]python 产生器 (Generator)”的想法;

发表评论

电子邮件地址不会被公开。 必填项已用*标注