在以下情况下使用生成器函数有什么好处?

What are the advantages of using a generator function in the following case?

我的作业objective是无限期地生成列表元素。 所以我这样做了:

SERVERS = ['APP1', 'APP2', 'APP3']
#SERVERS = ['APP1', 'APP2', 'APP3', 'APP4', 'APP5', 'APP6']
length = len(SERVERS)

def get_server():
    current_server = SERVERS.pop(0)
    SERVERS.append(current_server)
    return current_server

if __name__ == '__main__':
    for i in range(9):
        print get_server()

解决方案是这样的:

SERVERS = ['APP1', 'APP2', 'APP3']
#SERVERS = ['APP1', 'APP2', 'APP3', 'APP4', 'APP5', 'APP6']

def get_server():
    def f():
        while True:
            i = SERVERS.pop(0)
            SERVERS.append(i)
            yield i
    return next(f())

if __name__ == '__main__':
    for i in range(9):
        print get_server()

尽管两种情况下的输出相同:

codewingx@CodeLair:~/repo/python$ python load_balancer.py
APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

那么生成器函数有什么好处呢?

使用itertools.cycle()

生成器没有在这里添加任何有用的东西。我会尽量避免 pop(0) 因为它每次都会触发整个服务器列表的重建。

我会推荐 itertools.cycle():

from __future__ import print_function

from itertools import cycle

SERVERS = ['APP1', 'APP2', 'APP3']

servers = cycle(SERVERS)

for i in range(9):
    print(next(servers))

输出:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

我们包装在一个函数中以匹配您的用法:

def make_get_server():
    servers = cycle(SERVERS)
    def get_server():
        return next(servers)
    return get_server

get_server = make_get_server()

for i in range(9):
    print(get_server())

输出:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

编写您自己的生成器函数

为了说明生成器的意义,利用其存储 stet 能力的变体可能更有用:

def gen():
    index = 0
    end = len(SERVERS)
    while True:
        yield SERVERS[index]
        index += 1
        if index >= end:
            index = 0

虽然这很好地说明了您可以使用 index 的状态,但使用以下方法可以更轻松地实现同样的效果:

def gen():
    while True:
        for server in SERVERS:
            yield server

g = gen()

def get_server():
    return next(g)

这避免了修改 SERVERS 的列表。结果是一样的:

for i in range(9):
    print(get_server())

输出:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

发电机的工作原理

一个简单的生成器函数:

>>> def gen():
...     print('start')
...     yield 1
...     print('after 1')
...     yield 2
...     print('after 2')
...

创建实例:

>>> g = gen()

使用next获取yield返回的下一个值:

>>> next(g)
start
1

继续:

>>> next(g)
after 1
2

现在已经用完了:

>>> next(g)
after 2

StopIteration  next(g)

你可能会想到在生成器函数中移动的光标。每次调用 next() 时,它都会移动到下一个 yield。所以将 yield 放在 while True 循环中会产生一个无限生成器。只要您不对其调用 close(),它就会为您提供一个带有 yield 的新值。此外,您在生成器中有状态。这意味着你可以做一些事情,比如在调用 next().

之间增加计数器

在此上下文中,列表是它自己的生成器:

for i in SERVERS:
    do_something_with_element(i)

如果你想要一个无限的发电机,@MikeMüller 的 itertools.cycle 是首选,而不是重新发明轮子。如果你必须自己做:

def my_cycle(s):
    while True:
        for i in s:
             yield i

但是不要这样做,它的效率较低并且需要更多代码 reader 的内存。