生成器、迭代器、装饰器
生成器(generator)
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种generator,把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x*x for x in range(5)] >>> L [0, 1, 4, 9, 16] >>> g = (x*x for x in range(5)) >>> g <generator object <genexpr> at 0x01BB55A0>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator,我们可以直接打印出list的每一个元素,打印generator只能获得一个内存地址,那我们要怎么获得值呢?
- 调用next()或者__next__(),next是内置函数,__next__()是generator方法,generator保存的是算法,每次调用
next(g)或g.__next__(),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
>>> next(g) #等同于g.__next__() 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
- 上面这种不断调用
next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象。
>>> g = (x*x for x in range(5)) >>> for i in g: ... print(i) ... 0 1 4 9 16
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
- 用捕捉异常的方式try ..except..else..也可以实现
g = (x*x for x in range(5))
while True:
try:
num = next(g)
except StopIteration:
break
else:
print(num)
结果:
0
1
4
9
16
第二种generator,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现:
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
- 函数实现斐波拉契数列
def fib(n):
i,a,b=0,1,1
while i<n:
print(a)
a,b = b,a+b #等同于t= (b,a+b),a = t[0] b = t[1]
i+=1
fib(6)
结果:
1
1
2
3
5
8
- 上面的函数和generator仅一步之遥。要把
fib函数变成generator,只需要把print(b)改为yield b就可以了:
def fib(n):
i,a,b=0,1,1
while i<n:
yield a
a,b = b,a+b
i+=1
return '超出'
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
f = fib(6) print(f) 结果: <generator object fib at 0x01265540>
generator和函数的执行流程不一样,函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。那怎么取得值呢?同样有三种方法,这里就不写next的实现方式了。
for i in fib(6): #for实现方式
print(i)
结果:
1
1
2
3
5
8
-------------------------------------------------------
f = fib(6) #捕捉异常实现方式
while True:
try:
x = f.__next__()
except StopIteration as e:
print("Generator return value %s"%e.value)
break
else:
print(x)
结果:
1
1
2
3
5
8
Generator return value 超出
yield还可实现在单线程的情况下实现并发运算,详见代码
def customer():
while True:
s = yield #等待请求
if isinstance(s,str):#接收到请求后,开始处理请求
print("字符串")
elif isinstance(s,list):
print("列表:%s"%s)
def productor():
a = customer()
a.__next__() #不掉用next,只是把函数变成一个生成器
a.send("你知道我是什么类型嘛?") #发送信息
a.send([1,2,3,4,5])
productor()
迭代器(iterator)
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象
>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> isinstance({},Iterable)
True
>>> isinstance(3,Iterable)
False
>>> isinstance('123',Iterable)
True
>>> from collections import Iterator >>> isinstance([],Iterator) False >>> isinstance((x for x in range(5)),Iterator) True
Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。list、dict、str等Iterable变成Iterator可以使用iter()函数>>> from collections import Iterator >>> isinstance([],Iterator) False >>> isinstance(iter([]),Iterator) True
装饰器(decorator)
先了解清除几个概念:什么是高阶函数?什么是嵌套函数?
- 高阶函数
变量可以指向函数,下面以内置函数求绝对值函数abs为例
>>> abs(-10) 10 >>> abs <built-in function abs> >>> f= abs #变量f指向函数abs >>> f <built-in function abs> >>> f(-10) #调用函数 10
说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。
函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
如果把abs指向其他对象,会有什么情况发生?
>>> abs = 10 >>> abs(-10) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable >>> abs 10
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x,y,f):
return f(x)+f(y)
print(add(-1,3,abs))
结果:
4
编写高阶函数,就是让函数的参数能够接收别的函数。
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:
def calc_sum(*args):
ax = 0
for i in args:
ax+=i
return ax
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args):
def add():
ax = 0
for i in args:
ax += i
return ax
return add
分别调用calc_sum(1,2,3,4,5)和lazy_sum(1,2,3,4,5)是什么结果呢?
print(calc_sum(1,2,3,4,5)) print(lazy_sum(1,2,3,4,5)) 结果: 15 <function lazy_sum.<locals>.add at 0x01062198>
calc_sum直接返回计算结果
lazy_sum返回嵌套函数add的内存地址,lazy_sum即嵌套函数,那要怎么得到结果呢?很简单,和调用函数的方式一样
print(lazy_sum(1,2,3,4,5)()) #15
请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1,2,3,4,5) f2 = lazy_sum(1,2,3,4,5) print(f1) print(f2) 结果: <function lazy_sum.<locals>.add at 0x011D2198> <function lazy_sum.<locals>.add at 0x011D2108>
那装饰器是什么呢?
import time
def timer(func): #传入函数
print("我进入timer函数了")
def demo(): #嵌套函数
start_time = time.time()
func()
end_time = time.time()
print("func time is %s"%(end_time-start_time))
return demo #返回函数
@timer #装饰器@timer相当于text = timer(text)
def text(): #不加参数
time.sleep(1)
print("in the text")
text()
结果:
我进入timer函数了
in the text
func time is 1.0075056552886963
当我们调用text()函数时,实际上运行了两步,第一、text = timer(text),此时text指向了demo,第二、调用text(),那实际上就是调用demo()
装饰器升级版:每个函数可能都有不同的参数,那我们要怎么实现呢?
import time
def timer(func):
def demo(*args,**kwargs): #传入非固定参数
start_time = time.time()
func(*args,**kwargs)
end_time = time.time()
print("func time is %s"%(end_time-start_time))
return demo
@timer
def text(): #不加参数
time.sleep(1)
print("in the text")
@timer
def text2(name): #加参数
time.sleep(1)
print("in the text2!parameter is %s"%name)
text()
text2("lxj")
结果:
in the text
func time is 1.000171422958374
in the text2!parameter is lxj
func time is 1.000110387802124
装饰器终极版,装饰器传入参数。认证用户登录登陆user和bbs需进行密码验证
user1,word1 = 'lxj','123'
def my_key(key):
def wrapper(func):
def loggin(*args,**kwargs):
if key == 'local':
username = input("username:").strip()
password = input("password:").strip()
if username == user1 and password ==word1:
func(*args,**kwargs)
print("\033[32;1m验证成功\033[0m")
else:
print("\033[32;1m验证失败\033[0m")
elif key =='lbdr':
print("%s验证尚未激活该功能"%key)
return loggin
return wrapper
def home():
print("welcome to homepage!")
@my_key(key = 'local')
def user():
print("welcome to personal page!")
@my_key(key = 'lbdr') #装饰器带参数
def bbs():
print("welcome to bbs !")
home()
user()
bbs()
结果:
welcome to homepage!
username:lxj
password:123
welcome to personal page!
验证成功
lbdr验证尚未激活该功能
我们对调用uesr()进行剖析:当我们调用user时,实际上user = my_key('local')(user),首先是执行my_key('local'),返回函数wrapper,相当于指向wrapper,后面加(user),相当于调用wrapper(user),此时user指向了loggin,最后我们调用user()其实就是调用loggin()

浙公网安备 33010602011771号