您可以使用Python生成器函数做什么?

您可以使用Python生成器函数做什么?,python,generator,Python,Generator,我开始学习Python,我遇到了生成器函数,这些函数中有一个yield语句。我想知道这些函数真正擅长解决哪些类型的问题。请参阅中的“动机”部分 生成器的一个不明显的用途是创建可中断函数,它允许您在不使用线程的情况下“同时”(实际上是交错)执行更新UI或运行多个作业等操作。生成器为您提供延迟评估。您可以通过对它们进行迭代来使用它们,可以显式地使用“for”,也可以隐式地将其传递给任何进行迭代的函数或构造。您可以将生成器视为返回多个项目,就像它们返回一个列表一样,但它们不是一次返回所有项目,而是逐个

我开始学习Python,我遇到了生成器函数,这些函数中有一个yield语句。我想知道这些函数真正擅长解决哪些类型的问题。

请参阅中的“动机”部分


生成器的一个不明显的用途是创建可中断函数,它允许您在不使用线程的情况下“同时”(实际上是交错)执行更新UI或运行多个作业等操作。

生成器为您提供延迟评估。您可以通过对它们进行迭代来使用它们,可以显式地使用“for”,也可以隐式地将其传递给任何进行迭代的函数或构造。您可以将生成器视为返回多个项目,就像它们返回一个列表一样,但它们不是一次返回所有项目,而是逐个返回,并且生成器函数将暂停,直到请求下一个项目

生成器适用于计算大型结果集(特别是涉及循环本身的计算),您不知道是否需要所有结果,或者不希望同时为所有结果分配内存。或者对于生成器使用另一个生成器或消耗其他资源的情况,如果发生得越晚越好

生成器的另一个用途(实际上是相同的)是用迭代替换回调。在某些情况下,您希望函数执行大量工作,并偶尔向调用方报告。传统上,您会为此使用回调函数。将此回调传递给work函数,它将定期调用此回调。生成器方法是,功函数(现在是生成器)对回调一无所知,只在需要报告某个内容时生成。调用者没有编写单独的回调并将其传递给work函数,而是在生成器周围的一个小“for”循环中完成所有报告工作

例如,假设您编写了一个“文件系统搜索”程序。您可以完整地执行搜索,收集结果,然后一次显示一个结果。在显示第一个结果之前,必须收集所有结果,并且所有结果将同时保存在内存中。或者,您可以在查找结果时显示结果,这将更节省内存,对用户更友好。后者可以通过将结果打印函数传递给文件系统搜索函数来实现,也可以通过将搜索函数设置为生成器并迭代结果来实现

如果您想查看后两种方法的示例,请参阅os.path.walk()(带回调的旧文件系统遍历函数)和os.walk()(新的文件系统遍历生成器)。当然,如果您真的想将所有结果收集到一个列表中,那么生成器方法很容易转换为大列表方法:

big_list = list(the_generator)

基本上避免了在输入维护状态上迭代时的回调函数


有关使用generator可以做什么的概述,请参见和。

使用generator的原因之一是为了使某些解决方案的解决方案更清晰

另一种方法是一次只处理一个结果,避免建立大量的结果列表,而这些结果无论如何都要分开处理

如果您有一个fibonacci-up-to-n函数,如下所示:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result
for x in fibon(1000000):
    print x,
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()
您可以更轻松地编写函数,如下所示:

# generator version
def fibon(n):
    a = b = 1
    for i in xrange(n):
        yield a
        a, b = b, a + b
功能更清晰。如果您使用这样的函数:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result
for x in fibon(1000000):
    print x,
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()
在本例中,如果使用生成器版本,则根本不会创建整个1000000项列表,一次只创建一个值。使用列表版本时,情况并非如此,列表将首先创建。

我最喜欢使用的是“筛选”和“减少”操作

假设我们正在读取一个文件,只需要以“##”开头的行

然后我们可以在适当的循环中使用生成器函数

source= file( ... )
for line in filter2sharps( source.readlines() ):
    print line
source.close()
reduce示例与此类似。假设我们有一个需要定位
行块的文件。[不是HTML标记,而是恰好看起来像标记的行。]

def reduceLocation( aSequence ):
    keep= False
    block= None
    for line in aSequence:
        if line.startswith("</Location"):
            block.append( line )
            yield block
            block= None
            keep= False
        elif line.startsWith("<Location"):
            block= [ line ]
            keep= True
        elif keep:
            block.append( line )
        else:
            pass
    if block is not None:
        yield block # A partial block, icky

其思想是,生成器函数允许我们过滤或减少一个序列,一次生成一个值的另一个序列。

缓冲。如果在大数据块中提取数据是有效的,但在小数据块中处理数据,那么生成器可能会有帮助:

def bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

通过上述功能,您可以轻松地将缓冲与处理分开。消费者函数现在可以一个接一个地获取值,而不用担心缓冲。

一堆堆东西。任何时候,只要您想生成一系列项目,但又不想同时将它们“具体化”到一个列表中。例如,您可以使用一个简单的生成器返回素数:

def primes():
    primes_found = set()
    primes_found.add(2)
    yield 2
    for i in itertools.count(1):
        candidate = i * 2 + 1
        if not all(candidate % prime for prime in primes_found):
            primes_found.add(candidate)
            yield candidate
然后,您可以使用它生成后续素数的乘积:

def prime_products():
    primeiter = primes()
    prev = primeiter.next()
    for prime in primeiter:
        yield prime * prev
        prev = prime
这些都是相当简单的示例,但您可以看到它在处理大型(可能是无限的!)数据集时如何有用,而无需事先生成它们,这只是更明显的用途之一。

简单的解释: 考虑< <代码>语句< < /> > <代码>
for item in iterable:
   do_stuff()
很多时候,
iterable
中的所有项目从一开始就不需要在那里,但可以根据需要动态生成。这两种方法都可以更有效

  • 空间(您不需要同时存储所有项目)和
  • 时间(迭代可能在需要所有项目之前完成)
其他时候,你甚至不知道所有的项目提前。例如:

for command in user_input():
   do_stuff_with(command)
您无法事先知道所有用户的命令,但如果您有一个生成器处理您的命令,您可以使用这样一个很好的循环:

def user_input():
    while True:
        wait_for_command()
        cmd = get_command()
        yield cmd

使用生成器,您还可以在无限序列上进行迭代,这在容器上进行迭代时当然是不可能的。

当web服务器充当代理时,我使用生成器:

  • 客户端请求一个代理ur
      def generate_integers(N):
        for i in xrange(N):
        yield i
    
        In [1]: gen = generate_integers(3)
        In [2]: gen
        <generator object at 0x8117f90>
        In [3]: gen.next()
        0
        In [4]: gen.next()
        1
        In [5]: gen.next()
    
    def test():
        for i in xrange(5):
            val = yield
            print(val)
    
    t = test()
    
    # Proceed to 'yield' statement
    next(t)
    
    # Send value to yield
    t.send(1)
    t.send('2')
    t.send([3])
    
    def ResultGenerator(cursor, batchsize=1000):
        while True:
            results = cursor.fetchmany(batchsize)
            if not results:
                break
            for result in results:
                yield result
    
    return - returns only once
    yield - returns multiple times
    
    db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
    cursor = db.cursor()
    cursor.execute("SELECT domain FROM domains")
    for result in ResultGenerator(cursor):
        doSomethingWith(result)
    db.close()
    
    class Rect():
    
        def __init__(self, x, y, width, height):
            self.l_top  = (x, y)
            self.r_top  = (x+width, y)
            self.r_bot  = (x+width, y+height)
            self.l_bot  = (x, y+height)
    
        def __iter__(self):
            yield self.l_top
            yield self.r_top
            yield self.r_bot
            yield self.l_bot
    
    myrect=Rect(50, 50, 100, 100)
    for corner in myrect:
        print(corner)
    
    def genprime(n=10):
        for num in range(3, n+1):
            for factor in range(2, num):
                if num%factor == 0:
                    break
            else:
                yield(num)
    
    for prime_num in genprime(100):
        print(prime_num)