生成器和迭代器

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。

使用迭代器的优点

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。

迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
 
迭代器有两个基本的方法
next方法:返回迭代器的下一个元素
__iter__方法:返回迭代器对象本身

一、迭代器Iterators
迭代器仅是一容器对象,它实现了迭代器协议。它有两个基本方法:
1)next方法
返回容器的下一个元素
2)__iter__方法
返回迭代器自身

迭代器可使用内建的iter方法创建,见例子:
>>> i = iter(‘abc‘)
>>> i.next()
‘a‘
>>> i.next()
‘b‘
>>> i.next()
‘c‘
>>> i.next()
Traceback (most recent call last):
  File "<string>", line 1, in <string>
StopIteration:

class MyIterator(object):
  def __init__(self, step):
  self.step = step
  def next(self):
  """Returns the next element."""
  if self.step==0:
  raise StopIteration
  self.step-=1
  return self.step
  def __iter__(self):
  """Returns the iterator itself."""
  return self
for el in MyIterator(4):
  print el
--------------------
结果:
3
2
1
0

二、生成器Generators
从Python2.2起,生成器提供了一种简洁的方式帮助返回列表元素的函数来完成简单和有效的代码。
它基于yield指令,允许停止函数并立即返回结果。
此函数保存其执行上下文,如果需要,可立即继续执行。
例如Fibonacci函数:
def fibonacci():
  a,b=0,1
  while True:
  yield b
  a,b = b, a+b
fib=fibonacci()
print fib.next()
print fib.next()
print fib.next()
print [fib.next() for i in range(10)]
--------------------
结果:
1
1
2
[3, 5, 8, 13, 21, 34, 55, 89, 144, 233]

PEP Python Enhancement Proposal Python增强建议

tokenize模块
>>> import tokenize
>>> reader = open(‘c:/temp/py1.py‘).next
>>> tokens=tokenize.generate_tokens(reader)
>>> tokens.next()
(1, ‘class‘, (1, 0), (1, 5), ‘class MyIterator(object):/n‘)
>>> tokens.next()
(1, ‘MyIterator‘, (1, 6), (1, 16), ‘class MyIterator(object):/n‘)
>>> tokens.next()
(51, ‘(‘, (1, 16), (1, 17), ‘class MyIterator(object):/n‘)

例子:
def power(values):
  for value in values:
  print ‘powering %s‘ %value
  yield value
def adder(values):
  for value in values:
  print ‘adding to %s‘ %value
  if value%2==0:
  yield value+3
  else:
  yield value+2
elements = [1,4,7,9,12,19]
res = adder(power(elements))
print res.next()
print res.next()
--------------------
结果:
powering 1
adding to 1
3
powering 4
adding to 4
7

保持代码简单,而不是数据。
注意:宁可有大量简单的可迭代函数,也不要一个复杂的一次只计算出一个值的函数。

例子:
def psychologist():
  print ‘Please tell me your problems‘
  while True:
  answer = (yield)
  if answer is not None:
  if answer.endswith(‘?‘):
  print ("Don‘t ask yourself too much questions")
  elif ‘good‘ in answer:
  print "A that‘s good, go on"
  elif ‘bad‘ in answer:
  print "Don‘t be so negative"
free = psychologist()
print free.next()
print free.send(‘I feel bad‘)
print free.send("Why I shouldn‘t ?")
print free.send("ok then i should find what is good for me")
--------------------
结果:
Please tell me your problems
None
Don‘t be so negative
None
Don‘t ask yourself too much questions
None
A that‘s good, go on
None

虽然很早之前就接触yield这个词了,却一直是一知半解。趁现在有时间,把它研究一通再说。

含有yield的函数说明它是一个生成器,而不是普通的函数。当程序运行到yield这一行时,该函数会返回值,并保存当前域的所有变量状态;等到该函数下一次被调用时,会从上一次中断的地方开始执行,一直遇到下一个yield, 程序返回值, 并在此保存当前状态; 如此反复,直到函数正常执行完成。

我一开始还想不明白调用者与生成器之间的函数堆栈是怎么做到的,后来才大悟原来是用到了‘协程‘这个原理。协程可视为微线程,下面会结合例子来说明一下yield及协程的运行过程。假设定义了test方法:

[python]

def test(len):

i = 0

while i < len :

yield i

i += 1

我们来调用它看看输出:

>>> for i in test(5):
print i

输出:  
0
1
2
3
4
这场景是不是很类似 for i in xrange(len); 是的, xrange就是这么干的。 for .. in 的操作实际上是调用了生成器的next()方法,以上的调用过程可以等价为:

[python]

f = test(5)

print f.next()

print f.next()

print f.next()

print f.next()

print f.next()

输出结果与上次输出一致。
另外,在这次调用过程中,协程被创建了一次, 被唤醒了5次(通过next),被挂起了5次(通过yield), 最后协程退出并销毁。 大概就这些点了,有更深的理解再做补充。

生成器(Generator)

如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]

>>> L

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> g = (x * x for x in range(10))

>>> g

<generator object <genexpr> at 0x104feab40>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过generator的next()方法:

>>> g.next()

0

>>> g.next()

1

>>> g.next()

4

>>> g.next()

9

>>> g.next()

16

>>> g.next()

25

>>> g.next()

36

>>> g.next()

49

>>> g.next()

64

>>> g.next()

81

>>> g.next()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

StopIteration

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))

>>> for n in g:

...     print n

...

0

1

4

9

16

25

36

49

64

81

所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):

n, a, b = 0, 0, 1

while n < max:

print b

a, b = b, a + b

n = n + 1

上面的函数可以输出斐波那契数列的前N个数:

>>> fib(6)

1

1

2

3

5

8

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:

def fib(max):

n, a, b = 0, 0, 1

while n < max:

yield b

a, b = b, a + b

n = n + 1

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

>>> fib(6)

<generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

>>> def odd():

...     print ‘step 1‘

...     yield 1

...     print ‘step 2‘

...     yield 3

...     print ‘step 3‘

...     yield 5

...

>>> o = odd()

>>> o.next()

step 1

1

>>> o.next()

step 2

3

>>> o.next()

step 3

5

>>> o.next()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

StopIteration

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就报错。

回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:

>>> for n in fib(6):

...     print n

...

1

1

2

3

5

8

小结

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

时间: 2024-10-15 02:12:54

生成器和迭代器的相关文章

Python中生成器和迭代器的功能介绍

生成器和迭代器的功能介绍 1. 生成器(generator) 1. 赋值生成器 1. 创建 方法:x = (variable for variable in iterable) 例如:x = (i for i in range(10)) print(x) >>> <generator object <genexpr> at 0x00000000006B85C8> 返回值:generator #使用元祖推导式的时候回变成一个生成器. 2. 调用 方法:x.__nex

python成长之路12——生成器和迭代器

一. 什么是生成器和迭代器  使用一个可迭代的对象比一个列表的好处: 还记得前面的filter和map吗,他们的返回值是一个相应的对象,我们可以循环这个对象,就取到了每个对象元素,而且取完之后,这个元素就没啦,一边取值,一边垃圾回收,这样相对于返回值直接是一个列表,就大大的节省了内存.因为如果返回值是一个长度几万几十万的列表,会一下子在内存里开辟那么多的内存空间,但是如果生成的是一个filter或者map对象,就没有这个担心啦. 我们用一个小例子来进行对比: 1 #要用python2.7的环境

Python之路【第六篇】:Python基础(22)——生成器和迭代器

迭代器 迭 代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们 很少在迭代途中往后退.另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素.迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之 后,元素可以不存在或者被销毁.这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件. 特点: 访问者不需要关心迭代器内部的结构,仅需通过__next()__(Python2.x

Python学习之旅—Day07(生成器与迭代器)

前言 本篇博客主要专注于解决函数中的一个重要知识点--生成器与迭代器.不管是面试还是工作,生成器与迭代器在实际工作中的运用可以说是非常多,从我们第一天开始学习for循环来遍历字典,列表等数据类型时,我们就已经和生成器,迭代器打交道了!本篇博客从最基础的基本概念,例如容器,可迭代对象,生成器,迭代器的概念,到for循环是怎么工作的娓娓道来.希望本篇博客能够帮助大家切实掌握生成器与迭代器的使用与底层原理. 一.容器 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in

Python 第四篇:生成器与迭代器

一:生成器:Generator是具有next方法的一个函数, 一个函数在执行的过程中产生一个迭代器,这个函数就是生成器,迭代器里面内容需要使用函数__next__()方法去读取,如: def func(): with open("haproxy.cfg") as f: try: #正常执行的 时候 while True: line = next(f) print(line,end='') except StopIteration: #捕获到指定的异常抛出 print("已经完

Python之路-python(装饰器、生成器、迭代器、Json &amp; pickle 数据序列化、软件目录结构规范)

装饰器: 首先来认识一下python函数, 定义:本质是函数(功能是装饰其它函数),为其它函数添加附件功能        原则:        1.不能修改被装饰的函数的源代码.        2.不能修改被装饰的函数的调用方式. 1 def test(): 2 print('test') 3 print(test ) #表示是函数 4 test() #表示执行foo函数 <function test at 0x00595660>#表示的是函数的内存地址test#函数test执行结果 简单的装

python(七)字符串格式化、生成器与迭代器

字符串格式化 Python的字符串格式化有两种方式:百分号方式.format方式 1.百分号的方式 %[(name)][flags][width].[precision]typecode (name)      可选,用于选择指定的key flags          可选,可供选择的值有: +       右对齐:正数前加正好,负数前加负号: -        左对齐:正数前无符号,负数前加负号: 空格    右对齐:正数前加空格,负数前加负号: 0        右对齐:正数前无符号,负数前

python之路——第二块(装饰器、生成器、迭代器)

装饰器 def deco(count): def func(num): if num < 10: count(num) else: exit() return func @deco def count(num): a = 0 for i in range(num): a += i print(a) count(11) 注:deco(count)和下面的count(num)中的count只是一个形参,count可以用任何变量名替换,但是num是一定要有,因此传入的实参是被装饰函数,被装饰函数有nu

详解生成器、迭代器

1.迭代 要搞清楚什么关于迭代器,生成器,可迭代对象,前提是我们要理解何为迭代. 第一,迭代需要重复进行某一操作 第二,本次迭代的要依赖上一次的结果继续往下做,如果中途有任何停顿,都不能算是迭代. 下面来看看几个例子,你就会更能理解迭代的含义. # example1 # 非迭代count = 0while count < 10:    print("hello world")     count += 1 # example2 # 迭代count = 0while count &

【python-Day5(字符串格式化、生成器、迭代器)】

一.字符串格式化 方式1:占位符 --> % %[(name)][flags][width].[precision]typecode ● (name) 可选,用于选择指定的key ● flags 可选,可供选择的值有: + 右对齐:正数前加正好,负数前加负号: - 左对齐:正数前无符号,负数前加负号: 空格 右对齐:正数前加空格,负数前加负号: 0 右对齐:正数前无符号,负数前加负号:用0填充空白处 ● width 可选,占有宽度 ● .precision 可选,小数点后保留的位数 ● type