python基础之迭代器、生成器、装饰器
一、列表生成式
a = [0,1,2,3,4,5,6,7,8,9] b = [] for i in a: b.append(i+1) print(b) a = b print(a) ---------------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = [1,3,4,5,4,5,6,7,8,11] for index,i in enumerate(a): a[index] += 1 print(a) ------------------------------------------ [2, 4, 5, 6, 5, 6, 7, 8, 9, 12]
a = [1,2,3,4,5,6,7,8,9,10] a = map(lambda x:x+1, a) print(a) for i in a: print(i) ----------------------------------------------------- <map object at 0x000000C5343A32B0> 2 3 4 5 6 7 8 9 10 11
a = [i+1 for i in range(10)] print(a) -------------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
二、生成器
列表解析式(列表推导式)生成器generator
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
In [1]: l = [x*x for x in range(10)] In [2]: l Out[2]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] In [3]: g = (x*x for x in range(10)) In [4]: g Out[4]: <generator object <genexpr> at 0x0000003E8157CE58>
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值:
In [5]: next(g) Out[5]: 0 In [6]: next(g) Out[6]: 1 In [7]: next(g) Out[7]: 4 In [8]: next(g) Out[8]: 9
生成器保存的是算法,每次调用 next(g) ,就计算出 g 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
In [9]: g = (x * x for x in range(10)) In [10]: for n in g: ...: print(n) ...: 0 1 4 9 16 25 36 49 64 81
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
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 #相当于t = (b, a + b);a = t[0];b = t[1](t是一个tuple) n = n + 1 return 'done' fib(10) ---------------------------------------------------------------------- 1 1 2 3 5 8 13 21 34 55
fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
def fib(max): n, a, b = 0, 0, 1 while n < max: # print(b) yield b a, b = b, a + b #相当于t = (b, a + b);a = t[0];b = t[1](t是一个元组) n = n + 1 return 'done' print(fib(10)) --------------------------------------------------- <generator object fib at 0x0000001082BB7A20>
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
例子:通过yield实现在单线程的情况下并发运算的效果
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: Renyz 4 #_*_coding:utf-8_*_ 5 import time 6 def consumers(name): 7 print("%s 准备吃包子啦!" %name) 8 while True: 9 baozi = yield 10 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) 11 12 def producer(): 13 c1 = consumers('毕') 14 c2 = consumers('李') 15 c1.__next__() 16 c2.__next__() 17 print("开始做包子啦!") 18 for i in range(10): 19 time.sleep(1) 20 print("做了2个包子!") 21 c1.send(i) 22 c2.send(i) 23 24 producer() 25 # 通过生成器实现协程并行运算
三、迭代器
迭代是一个重复的过程,即每一次重复为一次迭代,并且每次迭代的结果都是下一次迭代的初始值
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
In [1]: from collections import Iterable In [2]: isinstance([],Iterable) Out[2]: True In [3]: isinstance({},Iterable) Out[3]: True In [4]: isinstance((),Iterable) Out[4]: True In [5]: isinstance('abc',Iterable) Out[5]: True In [7]: isinstance((x for x in range(10)),Iterable) Out[7]: True In [8]: isinstance(123,Iterable) Out[8]: False
而生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
*可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
In [9]: from collections import Iterator In [10]: isinstance((x for x in range(10)),Iterator) Out[10]: True In [11]: isinstance(123,Iterator) Out[11]: False In [12]: isinstance('abc',Iterator) Out[12]: False In [13]: isinstance((),Iterator) Out[13]: False In [14]: isinstance([],Iterator) Out[14]: False In [15]: isinstance({},Iterator) Out[15]: False
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
In [16]: isinstance(iter({}),Iterator) Out[16]: True In [17]: isinstance(iter([]),Iterator) Out[17]: True In [18]: isinstance(iter('abc'),Iterator) Out[18]: True
为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python的for
循环本质上就是通过不断调用next()
函数实现的
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器 #2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下: 'world'.__iter__ (4,5,6).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象? 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象 #4、文件类型是迭代器对象 open('a.txt').__iter__() open('a.txt').__next__() #5、总结: 迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
四、装饰器
写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展即:
对扩展是开放的,而对修改是封闭的。
因此,开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类或者函数内部进行任何修改。
装饰器就是:
在不修改源代码和调用方式的基础上给其增加新的功能,多个装饰器可以装饰在同一个函数上
1、无参装饰器
原函数
import time def bar(): time.sleep(1) print('这是bar函数') bar() ---------------------------------------------- 这是bar函数
# 无参装饰器 import time def timer(func): def deco(): start_time = time.time() res = func() #func()=bar() end_time = time.time() print('cost', end_time-start_time) return res return deco @timer #bar=deco |func=bar def bar(): time.sleep(1) print('这是bar函数') # bar = timer(bar) #bar=deco |func=bar bar()
2、有参装饰器
# 有参装饰器 import time def timer(func): def deco(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) end_time = time.time() print('cost', end_time-start_time) return res return deco @timer def bar(a, b): time.sleep(1) print('这是bar') print(a) print(b) bar(1,2)
3、带参数的装饰器
# 带参数的装饰器 def default_engine(engine=None): def auth(func): def deco(*args, **kwargs): user = input('user:') password = input('password:') if engine == 'mysql': if user == 'root' and password == 'root': res = func(*args, **kwargs) return res else: print('用户名或密码错误') else: print('没有这个引擎') return deco return auth @default_engine(engine='mysql') def index(): print('welcome to home page') # res = default_engine(engine='mysql') # index = res(index) index()
五、闭包
1、闭包定义
(1)闭包函数必须有内嵌函数;
(2)内嵌函数需要引用该嵌套函数上一级namespace中的变量;
(3)闭包函数必须返回内嵌函数;
通过这三点,就可以创建一个闭包;python装饰器就是使用了闭包
闭包的好处:使代码变得简洁
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,并且把里面的函数返回,我们把这种情况叫闭包
def test(num): #定义一个函数 def test_in(num2): #又定义一个函数 print(num + num2) return test_in #返回test_in里面的引用/就是闭包的结果 res = test(22) #22表示给test函数赋值,num=22 res(11) #11是参数num2的实参;加括号表示运行
例子:
def line_conf(x,y): def line(n): return x*n+y return line l1 = line_conf(3,8) print(l1(2))