Python入门基础第十八课--生成器

1.前言

    在结束毕业设计这个漫长而又艰辛的道路以后,最终取得了圆满的结果。顺利毕业,九月份再上研究生。虽然一段的路程走完了,但是前方的路还很长很长,未知的挑战还很多很多。让我们收拾收拾继续咯!今天这部分主要来看一下Python里面一个新引入的概念--生成器。它是什么?可以用来干什么?

2.生成器

2.1创建生成器

    生成器是一种用普通的函数语法定义的迭代器,生成器可以帮助读者写出非常优雅的代码。当然,编写任何程序时不使用生成器也是可以的。它具体的实现和内部机制在例子里面看起来会更加清晰一点。

nested=[[1,2],[3,4],[5,6]]

def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element

print list(flatten(nested))

    来看看这个例子,首先我们创建一个列表的列表,然后再建立一个展开嵌套列表的函数,函数的参数是列表。它应该具有这样的功能:按顺序打印出列表中的数字。这个函数每一行都很简单,一看就懂。特别注意红色字体标注的部分,如果我们将它换成“print element”是不是更容易理解一些。这里的yield是新的内容,任何包含yield语句的函数我们称为生成器。除了名字不一样外,它的具体行为和普通的函数也有很大的差别。这就在于它不是项return那样返回值,而是每次产生多个值。每次产生一个值(使用yield语句)后函数就会冻结:函数停在那点等待被重新唤醒。函数被重新唤醒后就从停止的那点开始执行。

2.2递归生成器

 在使用递归来解决多层嵌套的问题的时候,我们通常这样做:在生成器开始除添加一个检查语句。试着将传入的对象和一个字符串拼接,看看会不会出现TypeError,这是检查一个对象是不是类似于字符串的最简单、最快速地方法。我们来看看具体的例子:

def flatten(nested):
 try:
    try:nested + ''
    except TypeError:pass
    else: raise TypeError
    for sublist in nested:
        for element in flatten(sublist):
            yield element
 except TypeError:
     yield nested


print list(flatten(['foo',['bar',['baz']]]))

    其实上面的代码没有执行类型检查。这里没有测试nested是否是一个字符串,其实可以采用isinstance方法来完成检查,而是检查nested行为是不是像一个字符串(通过和字符串拼接来检查)。是不是一目了然,到这里生成器基本的用法相信你已经了解的差不多了。

    生成器是一个包含yield关键字的函数,当它被调用时候,在函数体内的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。return语句意味着生成器要停止执行,不再生成任何东西,return语句只有一个生成器中使用时才能进行无参数调用。

    生成器是由两个部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分,这两部分经常被合在一起叫做生成器。

2.3生成器的方法

    生成器的新特征在Python2.5引入,表现为生成器和“外部世界”进行交流的渠道,需要注意以下两点:

  • 外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数,要发送的“消息”--任意对象。
  • 在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值。也就是外部通过send方法发送进来的值。如果next方法被使用,则yield方法返回None。

    但是send方法只有在生成器挂起以后才会生效,也就是在yield语句第一次执行后。如果在此之前需要给生成器提供更多的信息,那么只需要使用生成器函数的参数。来看看具体的例子:

def repeater(value):
    while True:
        new=(yield value)
        if new is not None: value=new


r=repeater(42)
print r.next()
print r.send("Hello,World!")

    在使用返回值的时候,安全起见还是要闭合yield表达式。

    此外,生成器还有其他两个方法:

  • throw方法,用于在生成器内引发一个异常
  • close方法,用于停止生成器。

    其实close方法也是建立在异常的基础上的,它在yield运行处引发一个GenerationExit异常。所以如果需要在生成器内进行代码清理的话,则可以将yield语句放在try/finally语句中,我们还可以尝试捕捉GenerationExit异常。但是随后必须将其重新引发、引发另外一个异常或者直接返回。

 3.实际问题-八皇后

    既然我们学了关于生成器的这么多东西,我们来看一个具体的问题,这个问题同时也是深受喜欢的计算机科学谜题--八皇后的问题。简单的来说是这样的:有一个棋盘和八个要放上去的皇后。唯一的要求是皇后之间不能形成威胁,也就是说要把皇后放置成每个皇后都不能吃掉其他皇后的状态。也就是讲:在国际象棋8*8的棋盘上,要求在每一行放置一个皇后,且做到在竖方向、斜方向都没有冲突。国际象棋的棋盘如下图所示:


    我们应该怎样处理这样的问题呢?按一般的顺序来讲,我们先放置第一个皇后,然后再放置第二个皇后在合适的位置。以此类推,如果发现不能放置下一个皇后,就回溯到上一步,尝试将皇后放置到其他的位置去。直到最后将八个皇后放置棋盘合适的位置上结束。

    再进一步想想,这里我们只有八个皇后,假设我们有数目不确定的皇后想要放置在棋盘上,又改怎么办?我们马上来看!

  • 位置表示:一般的我们采用元组和序列两种方式俩表示皇后在棋盘里具体的位置坐标,在这里 我们选择元组。
  • 冲突寻找:冲突具体表现在下一个皇后和前面的皇后有同样的水平位置(水平距离为0),或者是在一条对角线(水平距离等于垂直距离)上,就会发生冲突。
  • 基本实现:你放置到最后一个皇后,你想让它干什么?假设你想找出所有可能的解决方案。这样一来,它就能根据其他皇后的位置生成它自己能占据的位置,也有可能没有合适的位置。就能把这样的情况表达出来。
  • 递归处理:接着上面的基本实现的情况,递归函数就会认为来自低层的结果都是正确的,程序从钱满的皇后的到了包含位置信息的元组,并且要为后面的皇后提供当前皇后的每种合法的位置信息,我们需要做的就是把当前的位置信息添加到元组中并传给后面的皇后。

下面为上述步骤的具体实现:

import random

def conflict(state,nextX):
    nextY=len(state)
    for i in range(nextY):
        if abs(state[i]-nextX) in (0,nextY-i):
            return True
    return False


def queens(num=8,state=()):
        for pos in range(num):
            if not conflict(state,pos):
                if len(state)==num-1:
                    yield (pos,)
                else:
                    for result in queens(num,state+(pos,)):
                        yield (pos,)+result


def prettyprint(solution):
    def line(pos,length=len(solution)):
        return '. '*(pos) + 'X' + '. ' *(length-pos-1)
    for pos in solution:
        print line(pos)


prettyprint(random.choice(list(queens(8)))

运行结果有如下参考:

        等等,我们可以在代码中测试,用8个皇后来运行queens,会看到有很多方案来供我们选择,执行len(list(queens(8)))会得到92种结果。

好咯,生成器的内容就介绍到这里咯,上面这个实际问题希望大家多做练习和理解。下一章节预告--模块 。

猜你喜欢

转载自blog.csdn.net/qq_34454366/article/details/80711582