在了解Python的数据结构时,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一块儿,不免让初学者一头雾水,我将用一篇文章试图将这些概念以及它们之间的关系捋清楚python
容器是一种把多个元素组织在一块儿的数据结构,容器中的元素能够逐个地迭代获取,能够用 in , not in 关键字判断元素是否包含在容器中。一般这类数据结构把全部的元素存储在内存中(也有一些特列并非全部的元素都放在内存)在Python中,常见的容器对象有:数据结构
list, deque, ....app
set, frozensets, ....socket
dict, defaultdict, OrderedDict, Counter, ....ide
tuple, namedtuple, …函数
str学习
容器比较容易理解,由于你就能够把它看做是一个盒子、一栋房子、一个柜子,里面能够塞任何东西。从技术角度来讲,当它能够用来询问某个元素是否包含在其中时,那么这个对象就能够认为是一个容器,好比 list,set,tuples都是容器对象:spa
尽管绝大多数容器都提供了某种方式来获取其中的每个元素,但这并非容器自己提供的能力,而是 可迭代对象 赋予了容器这种能力,固然并非全部的容器都是可迭代的。code
若是给定一个list或tuple,咱们能够经过for
循环来遍历这个list或tuple,这种遍历咱们称为迭代(Iteration)。orm
刚才说过,不少容器都是可迭代对象,此外还有更多的对象一样也是可迭代对象,好比处于打开状态的files,sockets等等。但凡是能够返回一个 迭代器 的对象均可称之为可迭代对象,听起来可能有点困惑,不要紧,可迭代对象与迭代器有一个很是重要的区别。先看一个例子:
这里 x 是一个可迭代对象,可迭代对象和容器同样是一种通俗的叫法,并非指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。 y 和 z 是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,好比 list_iterator , set_iterator 。可迭代对象实现了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),这两个方法对应内置函数 iter() 和 next() 。 __iter__ 方法返回可迭代对象自己,这使得他既是一个可迭代对象同时也是一个迭代器。
那么什么迭代器呢?它是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,任何实现了 __next__() (python2中实现 next() )方法的对象都是迭代器,至于它是如何实现的这并不重要。
如今咱们就以斐波那契数列()为例,学习为什么建立以及如何建立一个迭代器:
著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数均可由前两个数相加获得:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def fab(max):
= 0, 0, 1 n <= b, a += n + 1
直接在函数fab(max)中用print打印会致使函数的可复用性变差,由于fab返回None。其余函数没法得到fab函数返回的数列。
== 0, 0, 1 n <= b, a += n + 1 L
代码2知足了可复用性的需求,可是占用了内存空间,最好不要。
对比for i in range(1000): pass和for i in xrange(1000): pass,前一个返回1000个元素的列表,然后一个在每次迭代中返回一个元素,所以可使用迭代器来解决复用可占空间的问题
== 0, 0, 1 self.n <== self.b, self.a += self.n + 1
Fabs 类经过 next() 不断返回数列的下一个数,内存占用始终为常数
Fib既是一个可迭代对象(由于它实现了 __iter__ 方法),又是一个迭代器(由于实现了 __next__ 方法)。实例变量 self .a 和 self.b 用户维护迭代器内部的状态。每次调用 next() 方法的时候作两件事:
为下一次调用 next() 方法修改状态
为当前此次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人须要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
在大多数状况下,咱们不会一次次调用next方法去取值,而是经过 for i in (iterable),
注意:in后面的对象若是是一个迭代器,内部由于有iter方法才能够进行操做,因此,迭代器协议里面有iter和next两个方法,不然for语句没法应用。
注意:
1 2 |
|
生成器算得上是Python语言中最吸引人的特性之一,生成器实际上是一种特殊的迭代器,不过这种迭代器更加优雅。代码3远没有代码1简洁,生成器(yield)既能够保持代码1的简洁性,又能够保持代码3的效果。它不须要再像上面的类同样写 __iter__() 和 __next__() 方法了,只须要一个 yiled 关键字。 生成器有以下特征是它必定也是迭代器(反之不成立),所以任何生成器也是以一种懒加载的模式生成值。用生成器来实现斐波那契数列的例子是:
= 0, 0, 1 n <= b, a += n + 1
>>> n fab(51 1 2 3 5
fib 就是一个普通的python函数,它特需的地方在于函数体中没有 return 关键字,函数的返回值是一个生成器对象。当执行 f=fib(5) 返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
yield 的做用就是把一个函数变成一个 generator,带有 yield 的函数再也不是一个普通函数,Python 解释器会将其视为一个 generator,在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是彻底同样的,因而函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程当中被 yield 中断了数次,每次中断都会经过 yield 返回当前的迭代值。
也能够手动调用 fab(5) 的 next() 方法(由于 fab(5) 是一个 generator 对象,该对象具备 next() 方法),这样咱们就能够更清楚地看到 fab 的执行流程:
>>> f = fab(3>>> f.1 >>> f.1 >>> f.2 >>> f., line 1, <module>
须要明确的就是生成器也是iterator迭代器,由于它遵循了迭代器协议.
两种建立方式
生成器函数跟普通函数只有一点不同,就是把 return 换成yield,其中yield是一个语法糖,内部实现了迭代器协议,同时保持状态能够挂起。以下:
在一个生成器中,若是没有return,则默认执行到函数完毕;若是遇到return,若是在执行过程当中 return,则直接抛出 StopIteration 终止迭代.
def f():
yield 5
print("ooo")
return
yield 6
print("ppp")
# if str(tem)=='None':
# print("ok")
f=f()
# print(f.__next__())
# print(f.__next__())
for i in f:
print(i)
'''
return即迭代结束
for不报错的缘由是内部处理了迭代结束的这种状况
'''
= 1024=
若是直接对文件对象调用 read() 方法,会致使不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。经过 yield,咱们再也不须要编写读文件的迭代类,就能够轻松实现文件读取。
My:生成器对象就是一种特殊的迭代器对象,知足迭代器协议,能够调用next;对生成器对象for 循环时,调用iter方法返回了生成器对象,而后再不断next迭代,而iter和next都是在yield内部实现的。
s + i range(4= n [1, 10= (add(i, n) i list(base)
'''
核心语句就是:
for n in [1, 10]:
base = (add(i, n) for i in base)
在执行list(base)的时候,开始检索,而后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达
式的过程。必须紧紧把握住这一点。
生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i, n)绑定的是n这个
变量,而不是它当时的数值。
而后首先是第一次生成器表达式的执行过程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),这是第一次循环的结
果(形象表示,其实已经计算出来了(10,11,12,3)),而后第二次,
base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,终于获得结果了[20, 21, 22, 23].
'''
七 生成器的扩展
生成器对象支持几个方法,如gen.next() ,gen.send() ,gen.throw()等。
因为没有额外的yield,因此将直接抛出StopIteration。
send的工做方式:
(= 7 8=
协程应用:
所谓协同程序也就是是能够挂起,恢复,有多个进入点。其实说白了,也就是说多个函数能够同时进行,能够相互之间发送消息等。
x range(4 (+ x range(4 (+ = i = =