Python学习笔记8(迭代器生成器装饰器)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习笔记8(迭代器生成器装饰器)相关的知识,希望对你有一定的参考价值。
1.列表生成式
要想学习生成器和迭代器,首先得了解另外一个概念,列表生成式。
想要生成一个0~9的列表的时候,首先想到的就是range(0,10)
>>>a = range(0,10) >>>print(a) #3.0输出结果 range(0,10) #2.0输出结果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
在3.0的版本呢当中range只是用来生成一个迭代器了,但是在2.0的版本里可以使用range来快速生成list。
但是想要生成一个[1*1,2*2,3*3......10*10],怎么做呢?可以使用循环
1 >>>L=[] 2 >>>for x in range(1,11): 3 >>> L.append(x*x) 4 >>>print(L) 5 #输出结果 6 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
列表生成式,可以一步解决上述问题。
1 >>>L = [x*x for x in range(1,11)] 2 >>>print(L) 3 #输出结果 4 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
列表生成式后还可加入if判断条件,例如筛选出偶数的平方。
1 >>>L = [x*x for x in range(1,11) if x%2 ==0] 2 >>>print(L) 3 #输出结果 4 [4, 16, 36, 64, 100]
还可以嵌套两层循环,生成全排列。
1 >>>L = [x+y for x in ‘ABCD‘ for y in "xyz"] 2 >>>print(L) 3 #输出结果 4 [‘Ax‘, ‘Ay‘, ‘Az‘, ‘Bx‘, ‘By‘, ‘Bz‘, ‘Cx‘, ‘Cy‘, ‘Cz‘, ‘Dx‘, ‘Dy‘, ‘Dz‘]
这就是列表生成式。
2.生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有两种方法。
第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
1 >>>L1 = [x*x for x in range(10)] 2 >>>print(L1) 3 >>>L2 = (x*x for x in range(10)) 4 >>>print(L2) 5 #输出结果 6 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 7 <generator object <genexpr> at 0x0000012B35912E60>
创建L1和L2唯一的区别就是一个是[ ]一个是( ),L1是一个列表,L2就是一个生成器generator。
我们可以直接打印出列表L1的每一个元素,因为已经生成并且创建出来了,但是如何打印L2generator的元素呢?
可以使用next()函数获取generator的下一个返回值:
L2 = (x*x for x in range(10)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) print(next(L2)) #输出结果 0 1 4 9 16 25 36 49 64 81
此时生成器里的元素已经被取完,当再增加一个next()的时候,会抛出StopIteration
的错误。
1 Traceback (most recent call last): 2 File "D:/workspace/test/day1/hello.py", line 23, in <module> 3 print(next(L2)) 4 StopIteration
当然,我们也不会一直输入next()去获取它的每一个元素,而且结合for循环来迭代它,并且不需要关心StopIteration
的错误。
1 L2 = (x*x for x in range(10)) 2 for i in L2: 3 print(i) 4 5 0 6 1 7 4 8 9 9 16 10 25 11 36 12 49 13 64 14 81
第二种方法,如果函数定义中包含yield关键字,那么这个函数就不是普通函数了,而是一个geneartor。
例如,普通的斐波那契数列函数:
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 print(b) 5 a, b = b, a + b 6 n = n + 1 7 return ‘done‘
调用并且执行
1 a = fib(5) 2 print(a) 3 4 1 5 1 6 2 7 3 8 5 9 done
当把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 n += 1 return ‘done‘ a = fib(5) print(a) #输出为 <generator object fib at 0x000001EC67462E60>
生成器和普通函数区别:
普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回;
而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
而要访问函数中的每一个元素,依旧是使用for循环
1 for i in a: 2 print(i) 3 4 1 5 1 6 2 7 3 8 5
我们可以看到拿到了想要的前五个斐波那契数列,但是没有得到return返回值“done”,
如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中。
1 b = fib(5) 2 while True: 3 try: 4 x = next(b) 5 print(x) 6 except StopIteration as e: 7 print("return的返回值:",e) 8 break 9 10 1 11 1 12 2 13 3 14 5 15 return的返回值: done
关于异常的捕获,之后肯定会单独写一篇来详细介绍。
3.迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
而生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
你可能会问,为什么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 # 首先获得Iterator对象: 2 it = iter([1, 2, 3, 4, 5]) 3 # 循环: 4 while True: 5 try: 6 # 获得下一个值: 7 x = next(it) 8 except StopIteration: 9 # 遇到StopIteration就退出循环 10 break
4.装饰器
定义:本质就是函数,(装饰其他的函数)就是为其他函数添加附加功能。
原则:
1.不能修改被装饰函数的源代码
2.不能修改被装饰函数的调用方式
实现装饰器知识储备:
1.函数即“变量”
只需要理解函数像变量一样先定义,才能引用。定义的时候也会生成一个内存地址,供函数的内容存放。
函数名存储的就是函数体的内存地址。
2.高阶函数
a.把一个函数名当作实参传给另一个函数(可以在不修改被装饰函数源代码的情况下为其添加功能)。
b.返回值中包含函数名(不修改被装饰函数的调用方式)。
3.嵌套函数
高阶函数+嵌套函数==》装饰器
----------------------------------------------------我是分割线----------------------------------------------------
高阶函数在装饰器中的作用一:
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print("in the bar") 6 7 def test(func): 8 start_time = time.time() 9 func() 10 stop_time = time.time() 11 print("the func run time is %s"%(stop_time-start_time)) 12 13 test(bar)
原函数bar,把原函数名作为参数传递给test函数(其实是将bar函数的内存地址传递给了test函数),并且在test函数里加上括号就表示执行。
至此实现了在不改变函数源代码的情况下增加了某些功能(例如例子中增加统计运行时间),但是改变了调用方式,因为原来是bar()调用,现在改成了test(bai)这么调用,不符合装饰器的规则。
别急,后面还有变数。
高阶函数在装饰器中的作用二:
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print("in the bar") 6 7 def test(func): 8 print(func) 9 return func 10 11 bar = test(bar) 12 bar()
在装饰的函数return中返回函数名,并且重新赋值给bar,再加上括号进行调用。
这样就没有改变调用方式,依旧是bar(),并且执行了test函数里的print。
好像是达成了装饰器的原则,没有修改源代码,没有改变调用方式。
但是,仔细想一想,有什么地方不对呢。这样做相当于是不是只是在函数调用之前增加了一些然并卵的骚操作,和这样的效果类似呢?
1 def bar(): 2 time.sleep(3) 3 print("in the bar") 4 5 def test(func): 6 print(func) 7 8 test(bar) 9 bar()
也就是说,改变的是在函数运行之前的一些东西,并不是函数本身的功能。有人估计会说,那就把函数放在test里运行,类似于下面的:
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print("in the bar") 6 7 def test(func): 8 start_time = time.time() 9 func() 10 stop_time = time.time() 11 print("the func run time is %s"%(stop_time-start_time)) 12 return func 13 14 bar = test(bar) 15 bar()
这样,问题其实更大了。不想改变函数的调用方式,是不是意味着在执行bar()语句的时候,函数才会运行。但是,实际是在bar = test(bar)已经执行了一遍bar()函数(此时是带有新增加功能的)。而在执行bar()函数的时候又执行了一遍(此时是没有新增加功能的)。
别急,接下来,就用到所需要的第三个知识储备:嵌套函数了。
嵌套函数在装饰器中的作用一:
嵌套函数简单的讲就是函数中还有函数(废话。。。)
那么就给我们提供了一个思路,既然想在test函数中放入被装饰的函数,又不想在运行test的时候就运行了原函数,那么,是不是可以把这个函数放在嵌套的函数里呢?例如:
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print("in the bar") 6 7 def test(func): 8 def timmer(): 9 start_time = time.time() 10 func() 11 stop_time = time.time() 12 print("the func run time is %s"%(stop_time-start_time)) 13 return timmer 14 15 bar = test(bar) 16 bar()
在运行bar = test(bar)的时候,并没有运行timmer函数,对吧,因为test函数里并没有调用的指令呀,反而是返回了timmer函数的内存地址给了新命名的bar。
在运行bar()的时候,实际上是在运行timmer()!!! 这时候才运行了最原始的bar函数,并且新增加了一个统计运行时间的功能。
至此,装饰器所需要的知识储备已经的铺垫完了。可以达到不修改源代码,不修改调用方式,给原函数增加一些功能。
语法糖@
1 import time 2 3 def test(func): 4 def timmer(): 5 start_time = time.time() 6 func() 7 stop_time = time.time() 8 print("the func run time is %s"%(stop_time-start_time)) 9 return timmer 10 11 @test 12 def bar(): 13 time.sleep(3) 14 print("in the bar") 15 16 #bar = test(bar) 17 bar()
语法糖@test ==> bar = test(bar)
相当于帮你省去了重新给函数赋值的这么一个过程,想给哪个函数加就在哪个函数头部加上@装饰函数名,没有加的当然按原功能调用啦
这就是一个简单的装饰器例子。
关于参数
1 import time 2 3 def test(func): 4 def timmer(): 5 start_time = time.time() 6 func() 7 stop_time = time.time() 8 print("the func run time is %s"%(stop_time-start_time)) 9 return timmer 10 11 @test 12 def bar1(): 13 time.sleep(3) 14 print("in the bar") 15 16 @test 17 def bar2(name): 18 time.sleep(3) 19 print("name:",name) 20 21 bar1() 22 bar2(‘zhang‘)
原函数bar1和bar2都被增加了装饰器,不同的地方是bar2有个参数,这时候就有问题了。运行结果如下:
in the bar the func run time is 3.0000603199005127 Traceback (most recent call last): File "D:/workspace/test/day1/hello.py", line 22, in <module> bar2(‘zhang‘) TypeError: timmer() takes 0 positional arguments but 1 was given
原因在于,运行的bar2(),相当于运行的timmer()。在timmer()定义的时候没有给形参啊,肯定会报错,要想不报错,就给增加上不定长参数:*args和**kwargs
1 import time 2 3 def test(func): 4 def timmer(*args,**kwargs): 5 start_time = time.time() 6 func(*args,**kwargs) 7 stop_time = time.time() 8 print("the func run time is %s"%(stop_time-start_time)) 9 return timmer 10 11 @test 12 def bar1(): 13 time.sleep(3) 14 print("in the bar") 15 16 @test 17 def bar2(name): 18 time.sleep(3) 19 print("name:",name) 20 21 bar1() 22 bar2(‘zhang‘)
这样,无论原函数是几个参数,都能应对,这才是完整的装饰器。
简单提一嘴,如果装饰器想分情况装饰,在@test(传入参数),装饰器里会根据传入的参数分情况执行。但是具体怎么做呢?再最外面加一层嵌套,并且return第一层的函数名。
有兴趣的可以试一下。copy一个例子吧。
1 from threading import Thread 2 import time 3 4 class TimeoutException(Exception): 5 pass 6 7 ThreadStop = Thread._Thread__stop#获取私有函数 8 9 def timelimited(timeout): 10 def decorator(function): 11 def decorator2(*args,**kwargs): 12 class TimeLimited(Thread): 13 def __init__(self,_error= None,): 14 Thread.__init__(self) 15 self._error = _error 16 17 def run(self): 18 try: 19 self.result = function(*args,**kwargs) 20 except Exception,e: 21 self._error =e 22 23 def _stop(self): 24 if self.isAlive(): 25 ThreadStop(self) 26 27 t = TimeLimited() 28 t.start() 29 t.join(timeout) 30 31 if isinstance(t._error,TimeoutException): 32 t._stop() 33 raise TimeoutException(‘timeout for %s‘ % (repr(function))) 34 35 if t.isAlive(): 36 t._stop() 37 raise TimeoutException(‘timeout for %s‘ % (repr(function))) 38 39 if t._error is None: 40 return t.result 41 42 return decorator2 43 return decorator 44 45 @timelimited(2) 46 def fn_1(secs): 47 time.sleep(secs) 48 return ‘Finished‘ 49 50 if __name__ == "__main__": 51 print fn_1(4)
以上是关于Python学习笔记8(迭代器生成器装饰器)的主要内容,如果未能解决你的问题,请参考以下文章
python-学习笔记之-Day5 双层装饰器 字符串格式化 python模块 递归 生成器 迭代器 序列化