blog link: http://fupinyou.com/
好久没有在这儿写字了,感觉已经要长草啦!
近段时间倒是看了不少分享Python的文章,发现大多是一些速成手册,少有对于一个问题深究下去的,这类文章知识点过于琐碎,难于消化成为自己的。所以我想写的文章是对于一个问题深入剖析,let's go!
为了理解什么是yield,首先要理解什么是生成器(generators);而为了理解生成器,你还得先知道可迭代对象(iterables)。
可迭代对象(Iterables)
当你创建一个list对象时,你可以对其进行迭代访问:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist是一个可迭代对象。当你用列表推导时,你也是创建了一个可以进行迭代的list对象:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
任何你可以用在"for... in..."语句里面的都是可迭代对象,比如:列表对象(lists),字符串对象(strings),甚至是文件对象(files)。
这种迭代方式真是太方便了!因为所有的值都是已经存在了,你可以读取他们无数次。但是这样有一个不好的地方就是所有的值都是存在内存里面的,当值非常多的时候,会占用很大的内存空间,然而往往你不会想要取出所有的值,你只是访问里面的一部分值,这简直太浪费了。
生成器(Generators)
生成器是一中迭代器,但是特殊的是,你只能够迭代它们一次,因为生成器并不会将所有的值存在内存里面,生成器只是在运行的过程中计算出来一个一个值:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
只是将最外层的[]换成了()就创建了一个生成器对象。但是你不能对mygenerator进行第二次"for i in mygenerator",因为生成器只能用一次的。它先计算出0,然后忘掉,再计算出1,忘掉,最后计算出4,就这样一个接一个的计算下去。
yield
yield这个关键字选的真是非常准确,赞一下Python的设计者们,因为yield这个单词的中文意思有“产生”和“让步”两个意思,“产生”指的就是含有yield关键字的函数会返回一个生成器对象,“让步”是yield在协程里面的概念,这里咱先不详细讨论协程的细节(又是一个宏大的话题,一时半会儿说不清楚的。。。)。
yield的意义和return有点像,只不多yield返回的对象是比较特殊,是生成器对象:
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
生成器在上面这个例子中发挥的作用很小,但是当你的函数需要返回一个超大的集合并且这个集合只是被 用一次的时候,生成器的优势就能体现出来了。
想要掌握yield,你必须要知道的一点就是:当你调用含有yield关键字的函数时,函数体并没有执行,仅仅只是返回了一个生成器对象。当你用"for"对其迭代时,函数内容才会被执行。
当你第一次对其进行迭代时,函数内容会执行,直到遇到yield,函数返回第一个值,然后暂停;当你再次迭代时,函数体会再次执行直到遇到yield返回一个值,直到没有值可返回为止。
当函数体从未遇到yield时,生成器对象就被认为是空的。
控制生成器
>>> class Bank(): # 创建一个银行类
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # 没有遇到金融危机时,你可以无限地取出钞票
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # 金融危机时期,再也没有钱了
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 即便是新的ATM机(生成器对象),也不能产生值(钱)
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # 金融危机消除后,生成器还是不能产生值,除非重新创建生成器
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # 重新创建生成器对象
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
生成器在控制资源的访问权限时时非常有用的。
迭代的内部机制
迭代就是对可迭代对象(实现了__iter__()方法)和迭代器(实现了__next__()方法)进行操作,这就是Python里面的鸭子模型,只要一个类实现了迭代协议,那么这个类的对象就是可用于迭代的。另外,对一个可迭代对象调用iter()函数会产生一个迭代器,这个迭代器也只能被迭代一次,但是它并不是生成器。