Python函数之装饰器&列表生成式&迭代器&生成器
一 装饰器
1.1 什么是装饰器
装饰器本身可以是任意可调用对象,被装饰者也可以是任意可调用对象
装饰器的原则:
- 不修改被装饰对象的源代码
- 不修改被装饰对象的调用方式
装饰器的目标:在遵循前面两点的前提下,为被装饰对象添加上新功能
其实装饰器就是在闭包的基础上多进行了几步,如下:
def decorator(func): # 装饰函数
def wrapper():
print('在传入函数执行前做一些操作')
func() # 执行函数
print('在传入函数执行后做一些操作')
return wrapper
def login(): # 被装饰函数
print('登录成功')
login = decorator(login) # 将被装饰函数传入装饰函数中,并覆盖了原函数的入口
login() # 此时执行的就是被装饰后的函数
# 执行结果
在传入函数执行前做一些操作
登录成功
在传入函数执行后做一些操作
更多闭包和装饰器可参考: http://www.jb51.net/article/86383.htm
1.2 为何要使用装饰器
开放封闭原则:简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块不应该被修改
- 开放:对现有功能的扩展开放
1.3 装饰器语法
# 被装饰函数的正上方,单独一行采用@+装饰函数
@deco1
@deco2
@deco3
def foo():
pass
# 上面的结果等价于:
foo = deco1(deco2(deco3(foo)))
1.4 装饰器的应用
def timer(func): def wrapper(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) stop_time = time.time() print('foo函数执行时间: %.2f' %(stop_time - start_time)) return res return wrapper @timer # 相当于执行timer(foo) def foo(second): time.sleep(second) print('foo函数执行完成') return True res = foo(1) print(res)
def data_source(data='file'): def auth(func): def wrapper(*args,**kwargs): name = input("Please input username:>> ") pwd = input("Please input password:>> ") if data == 'file': if name == 'joe' and pwd == '123': print('login successful') res = func(*args,**kwargs) return res elif data == 'xxx': print('xxx') return wrapper return auth @data_source(data='file') # 实际执行效果:login = data_source(data='file')(login) def login(): print('执行完成') login()
有参装饰器说明:
上面的例子的中,有参装饰器的实际执行的效果为:
login = data_source(data='file')(login)
我们来剖析上面的语句,首先执行data_source(data='file'),返回的是auth函数,再调用返回的函数,参数是login函数,返回值最终是wrapper函数。
有参装饰器多用在:当一个函数不需要加装饰器的时候,如果直接把装饰器去掉,代码太多去掉显得麻烦而且防止后续会再次使用,这时我们可以利用有参装饰器去装饰它,具体代码如下:
def outer(flag): def decoreator(func): def wrapper(*args,**kwargs): if flag: print('装饰器函数生效') ret = func(*args,**kwargs) else: print('装饰器函数不使用') ret = func(*args, **kwargs) return ret return wrapper return decoreator f = False # 去掉装饰器 # f = True # 加装装饰器 @outer(f) def func(): print('原函数执行') func()
def decoreator1(func): def wrapper(*args,**kwargs): print('装饰器1执行开始') ret = func(*args, **kwargs) # 此时的func变为了decoreator2中的wrapper()函数 print('装饰器1执行结束') return ret return wrapper def decoreator2(func): def wrapper(*args,**kwargs): print('装饰器2执行开始') ret = func(*args, **kwargs) # 被装饰函数func() print('装饰器2执行结束') return ret return wrapper @decoreator1 @decoreator2 def func(): # 等价于decoreator1(decoreator2(func)) print('原函数执行') func() # 执行结果 装饰器1执行开始 装饰器2执行开始 原函数执行 装饰器2执行结束 装饰器1执行结束
time = [] def decoreator(func): time.append(func) # 统计当前程序中有多少个函数被装饰了 def warpper(*args,**kwargs): # time.append(func) # 统计本次程序执行有多少个带装饰器的函数被调用 ret = func(*args,**kwargs) return warpper @decoreator def func1(): pass @decoreator def func2(): pass @decoreator def func3(): pass func1() print(time)
1.5 wraps
先看下面一个例子:
def decorator(func):
def wrapper(*args,**kwargs):
func(*args,**kwargs)
return wrapper
@ decorator
def login():
pass
print(login.__name__) # wrapper
经过装饰器装饰过后,函数对象的__name__已经从原来的'login'变成了'wrapper',想要其不改变,又能完成装饰器功能,可以这样做:
from functools import wraps # 导入wraps,模块导入后面会详讲
def decorator(func):
@wraps(func) # 加在最内层函数正上方
def wrapper(*args,**kwargs):
func(*args,**kwargs)
return wrapper
@ decorator
def login():
pass
print(login.__name__) # login
login()
二 列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
举个例子,要生成[1,2,3,4,5,6,7,8,9,10],这样一个列表,我们可以这样做:
>>> list(range(1, 11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
如果要生成[1x1, 2x2, 3x3, ..., 10x10],我们可以通过循环这样做:
l = []
for i in range(1,11):
l.append(i*i)
print(l)
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
[x * x for x in range(1, 11)]
for循环后面还可以加上if判断,比如:我们在上面的例子中筛选出仅偶数的平方:
[x * x for x in range(1, 11) if x % 2 == 0]
还可以使用两层循环:
l = [int(i)*int(j) for i in '123' for j in '456'] print(l)
等价于:
l=[]
for i in '123':
for j in '456':
l.append(int(i)*int(j))
print(l)
运用列表生成式,可以写出非常简洁的代码。
补充说明:也存在字典生成式(推导式)和集合生成式(推导式),具体如下:
# 将一个字典的key和value对调
dic = {'a':1, 'b':2, 'c':3}
ret = {dic[i]:i for i in dic}
print(ret) # {'1': 'a', '2': 'b', 3: 'c'}
# 将一个字典的value加值
dic = dic = {'a':1, 'b':2, 'c':3}
ret = {i:dic[i]+1 for i in dic}
print(ret) # {'a': 2, 'c': 4, 'b': 3}
dic = dic = {'a':'1', 'b':'2', 'c':'3'}
ret = {i:dic[i]+'1' for i in dic}
print(ret) # {'a': '11', 'c': '31', 'b': '21'}
# 计算列表中每个值的平方,自带去重功能
L = [1,2,3,4,5,-2,-4]
print({i**2 for i in L})
三 迭代器
3.1 为什么有迭代器
对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,如果想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器
3.2 什么是可迭代对象
可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__()。可迭代对象都可以使用for循环来遍历其包含的元素,比如:列表、元组、字符串等。
L = [1, 2, 3, 4, 5]
L.__iter__() # 存在__iter__()方法
for i in L:
print(i)
怎么判断一个对象是不是可迭代对象?
方法一:查看该对象是否存在__iter__()方法
方法二:通过collections模块的Iterable类型判断
from collections import Iterable L = [1, 2, 3, 4, 5] print(isinstance(L,Iterable)) # True
3.3 什么是迭代器对象
迭代器对象指的是内置即有__iter__()方法又有__next__()方法的对象,比如:文件对象
这里需要说明的是:迭代器一定是可迭代对象,但可迭代对象不一定是迭代器对象,我们可以用collections模块的Iterator类型进行判断一个对象是否为迭代器,同时可以使用iter()方法将可迭代对象装换为迭代器。
from collections import Iterable,Iterator L = [1, 2, 3, 4, 5] print(isinstance(L,Iterable)) # True print(isinstance(L,Iterator)) # False L = iter(L) # 将可迭代对象转换为迭代器,等价于:L.__iter__() print(isinstance(L,Iterable)) # True print(isinstance(L,Iterator)) # True
3.4 迭代器的使用
Python的迭代器对象表示的是一个数据流,它可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。我们可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以迭代器的计算是惰性的,只有在需要返回下一个数据时它才会计算
L = [1, 2, 3, 4, 5] L = iter(L) # 将可迭代对象转换为迭代器,等价于:L.__iter__() print(next(L)) # 等价于:L.__next__() print(next(L)) print(next(L)) print(next(L)) print(next(L)) print(next(L)) # 报错:StopIteration
我们可以加入异常处理:
L = [1, 2, 3, 4, 5]
L = iter(L) # 将可迭代对象转换为迭代器,等价于:L.__iter__()
while True:
try:
print(next(L)) # 等价于:L.__next__()
except StopIteration:
break
这样我们就可以不依赖索引迭代取值了。
上面这种不断调用next(L)实在是坑了,常用的方法是使用for循环,这时我们根本不需要关心StopIteration的错误:
L = [1, 2, 3, 4, 5]
L = iter(L) # 将可迭代对象转换为迭代器,等价于:L.__iter__()
for i in L:
print(i)
3.5 迭代器的优缺点
优点:
1. 迭代器提供一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象,比如:字典、集合、文件等
2. 迭代器与列表比较,迭代器是惰性计算的,更省内存
缺点:
1. 永远无法获取迭代器的长度使用不如列表索引取值灵活
2. 迭代器不能向后移动,不能回到开始,一次性的
3.6 总结
凡是可作用于for循环的对象都是Iterable类型
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
集合数据类型如:list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
Python的for循环本质上就是通过不断调用next()函数实现的,针对可迭代对象for循环内部事实上是先调用iter()把Iterable变成Iterator再进行循环迭代的。例如:
L = [1, 2, 3, 4, 5]
for i in L:
print(i)
# 完全等价于
L = [1, 2, 3, 4, 5]
it = iter(L) # 首先获得Iterator对象
# 循环:
while True:
try:
x = next(it) # 获得下一个值:
except StopIteration:
break # 遇到StopIteration就退出循环
四 生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。它的本质就是一个迭代器
4.1 生成器的创建
要创建一个generator,有很多种方法,下面依次进行介绍。
方式一:将列表生成式的[]改成()
该方式我们也成为:生成器表达式
g = (x * x for x in range(1,11)) print(g) # <generator object <genexpr> at 0
此时的g就是一个生成器对象,我们怎么打印出生成器的每一个元素呢?因为生成器本质上就是一个迭代器,所以,可以通过next()函数获得生成器的下一个返回值:
g = (x * x for x in range(1,11)) print(g) # <generator object <genexpr> at 0x00000200ED65E4C0> print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) # 值取完后,报错:StopIteration
当然我们也可以使用for循环,而且是最常用的使用方式:
g = (x * x for x in range(1,11))
for i in g:
print(i)
方式二:yeild
先看一个例子:斐波拉契数列(Fibonacci)
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
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:
yield b
a, b = b, a + b
n = n + 1
这就是定义生成器的另外一种方式--采用关键字yield。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator,函数名()得到的结果就是生成器:
print(fib(10)) # <generator object fib at 0x000001BFF7C6E468>
同样的,我们可以通过for循环依次取值:
for i in fib(10):
print(i)
需要注意的是:generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
这里还有一个问题,如果我们定义的生成器函数存在返回值,利用for循环调用时,无法接收返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'Done'
g = fib(10)
while True:
try:
print(next(g))
except StopIteration as e:
print('生成器返回值为:%s' %e.value)
break
4.2 yield from
def func():
# for i in [1,2,3,4,5]:
# yield i
yield from [1,2,3,4,5] # 等价于上面的for循环
g = func()
print(next(g))
print(next(g))
print(next(g))
yield与return的区别:
return只能返回一次函数就彻底结束了,而yield能返回多次值
yield到底干了什么事?
1. 把函数变成生成器-->迭代器。相当于把__iter__和__next__方法封装到函数内部
2. 用return只能返回一次,而yield返回多次
3. 函数在暂停以及继续下一次运行时的状态是由yield保存
yield总结:
1. 把函数做成迭代器
2. 对比return,可以返回多次值,可以挂起/保存函数的运行状态
4.3 协程函数
先来看一个例子:
# yield关键字的另外一种使用形式:表达式形式的yield
from urllib.request import urlopen
def get():
while True:
url = yield # 依次接收send过来的值
ret = urlopen(url).read()
print(ret)
g = get()
g.send(None) # 对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g)
g.send('https://www.hao123.com/')
g.send('http://sports.qq.com/nba/')
g.send和next()的区别:
1. 如果函数内yield是表达式形式,那么必须先next(e)或e.send(None)
2. 二者共同之处是都可以让函数在上次暂定的位置继续运行,不一样的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值
4.4 实例应用
模仿range()函数的实现:
def my_range(start, end, step=1): while start < end: yield start start += step obj = my_range(0,10,2) # <generator object my_range at 0x000001D87BAFE468> for i in obj: print(i)

浙公网安备 33010602011771号