老生常谈Python之装饰器、迭代器和生成器


Posted in Python onJuly 26, 2017

在学习python的时候,三大“名器”对没有其他语言编程经验的人来说,应该算是一个小难点,本次博客就博主自己对装饰器、迭代器和生成器理解进行解释。

为什么要使用装饰器

什么是装饰器?“装饰”从字面意思来谁就是对特定的建筑物内按照一定的思路和风格进行美化的一种行为,所谓“器”就是工具,对于python来说装饰器就是能够在不修改原始的代码情况下给其添加新的功能,比如一款软件上线之后,我们需要在不修改源代码和不修改被调用的方式的情况下还能为期添加新的功能,在python种就可以用装饰器来实现,同样在写代码的时候也要考虑到后面的可扩展性,下面我们来看一步一步的看一下python的装饰器。

一个简单例子引入无参装饰器

先来看简单的几行代码,代码的运行结果是先睡2秒,再打印"hello boy!":

import time
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

我们现在我们需要为其添加一个程序计时功能,但是不能修改原始的代码:

import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
#运行结果
Hello boy!
Run time is 2.000446

看!我们没有修改原来的代码就实现了这个功能,因为函数也是对象,所以能够将函数foo当做参数传递给了函数timmer。

在python中,有个更简洁的方式来取代foo=timmer(foo),使用@timmer这种方式,这个在python中被称为语法糖。

import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
@timmer  #等于 foo=timmer(foo)
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

下面我们来一步一步的分析函数的执行过程:

1.导入time模块

import time

2.定义函数timmer,定义函数并不会执行函数内的代码

def timmer(func):

3.调用装饰器,相当于foo=timer(foo),就是把函数foo作为参数穿给了函数timmer

@timmer

4.运行函数timmer,接受了参数 func=foo

def timmer(func):

5.在函数timmer内,定义了函数wrapper,wrapper函数内部代码也不执行,然后将函数wrapper作为返回值返回

return wrapper

6.将返回值赋值给了foo,在第3步中,foo=timmer(foo),还记吧

@timmer #等于 foo=timmer(foo)

7.运行函数foo(),但是这里的函数已经不是原来的那个函数了,可以打印foo,对的,因为之前我们将wrapper作为返回值传给了foo,所以在这里执行foo就是在执行wrapper了,为了再确定这一点你也可打印wrapper,它们的内存地址相同,所以都是指向同一个地址空间:

<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #打印foo的结果
<function timmer.<locals>.wrapper at 0x000001F10AD8A950> #打印wrapper的结果
foo()

8.运行函数wrapper,记录开始时间,执行函数func,在第4步的时候,func被foo赋值,运行func就是在运行原函数foo,睡2秒,打印字符串;

time_start=time.time()
 time.sleep(2)
 print("Hello boy!")

9.记录结束时间,打印运行时间,程序结束。

Hello boy!
Run time is 2.000161

有参装饰器

在前面的例子中,原函数没有参数,下面的来看一个当原函数有参数,该怎么修改装饰器函数呢?

import time
def timmer(func):
 def wrapper(*args,**kwargs):
  """计时功能"""
  start_time=time.time()
  res=func(*args,**kwargs)
  end_time=time.time()
  print("Run time is %f"%(end_time-start_time))
  return res
 return wrapper
@timmer 
def my_max(x,y):
 """返回两个值的最大值"""
 res=x if x > y else y
 time.sleep(2)
 return res
res=my_max(1,2)
print(res)
#运行结果
Run time is 2.000175

当原函数有需要传入参数的时候,在这个例子my_max有两个位置形成需要传入参数,只需要在wrapper上添加两个形参,本例子中使用了可变参数(*args,**kwargs)也是可以的,这是@timmer就等于my_max(1,2)=timmer(my_max)

下面我们来看一个带有参数的装饰器:

def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == '123456' and username == 'Frank':
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == 'SQL':
    print("No SQL")
  return wrapper
 return auth2
@auth(filetype='file') #先先返回一个auth2 ==》@auth2 ==》 index=auth2(index) ==》 index=wrapper
def index():
 print("Welcome to China")
index()

如果装饰器本身有参数,就需要多一层内嵌函数,下面我们一步一步分析执行流程:

1.定义函数auth

def auth(filetype):

2.调用解释器,首先要运行函数auth(filetype='file')

@auth(filetype='file')

3.运行函数auth,定义了一个函数auth2,并作为返回值返回,那么这个@auth(filetype='file')就等同于@auth2,等同于index=auth2(index)

def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
  return wrapper
 return auth2

4.auth2(index)执行,func=index,定义函数wrapper,并返回之,这时候index其实就是等于wrapper了

def wrapper(*args,**kwargs):
return wrapper

5.当运行index,即运行wrapper,运行函数内部代码,filetype=="file",提示用户输出用户名和密码,判断输入是否正确,如果正确,则执行函数func(),等于执行原来的index,打印

if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == '123456' and username == 'Frank':
     print("Login successful")
     func()

6.运行结果测试

Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China

装饰器也是可以被叠加的:

import time
#
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
  # print("---",wrapper)
 return wrapper
def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == '123456' and username == 'Frank':
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == 'SQL':
    print("No SQL")
  return wrapper
 return auth2
@timmer
@auth(filetype='file') #先先返回一个auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper
def index():
 print("Welcome to China")
index()

#测试结果
Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China
Run time is 7.966267

注释优化

import time
def timmer(func):
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)

#运行结果
Welcome to China!
Run time is 1.0005640983581543:
计算程序运行时间

当我们使用了装饰器的时候,虽然没有修改代码本身,但是在运行的时候,比如上面这个例子,运行my_index其实在运行wrapper了,如果我们打印my_index的注释信息,会打印wrapper()的注释信息,那么该怎么优化?

可以在模块functools中导入wraps,具体见以下:

import time
from functools import wraps
def timmer(func):
 @wraps(func)
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)
#运行结果
Welcome to China!
Run time is 1.0003223419189453:
打印欢迎

这样,在表面看来,原函数没有发生任何变化。

为什么要用迭代器

从字面意思,迭代就是重复反馈过程的活动,其目的通常是为了比较所需目标或结果,在python中可以用迭代器来实现,先来描述一下迭代器的优缺点,如果看不懂可以先略过,等看完本博客再回头看,相信你会理解其中的意思:

优点:

迭代器在取值的时候是不依赖于索引的,这样就可以遍历那些没有索引的对象,比如字典和文件

迭代器与列表相比,迭代器是惰性计算,更节省内存

缺点:

无法获取迭代器的长度,没有列表灵活

只能往后取值,不能倒着取值

什么是迭代器

那么在python什么才算是迭代器呢?

只要对象有__iter__(),那么它就是可迭代的,迭代器可以使用函数next()来取值

下面我们来看一个简单的迭代器:

my_list=[1,2,3]
li=iter(my_list)  #li=my_list.__iter__()
print(li)
print(next(li))
print(next(li))
print(next(li))
#运行结果
<list_iterator object at 0x000002591652C470>
2

可以看到,使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值,一次值取一个值,当值取完了,再使用一次next()的时候,会报异常StopIteration,可以通过异常处理的方式来避免,try-except-else就是一个最常用的异常处理结构:

my_list=[1,2,3]
li=iter(my_list)
while True:
 try:
  print(next(li))
 except StopIteration:
  print("Over")
  break
 else:
  print("get!")
#运行结果
get!
get!
get!
Over

查看可迭代对象和迭代器对象

使用Iterable模块可以判断对象是否是可迭代的:

from collections import Iterable
s="hello" #定义字符串
l=[1,2,3,4] #定义列表
t=(1,2,3) #定义元组
d={'a':1} #定义字典
set1={1,2,3,4} #定义集合
f=open("a.txt") #定义文本
# 查看是否都是可迭代的
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
#运行结果
True
True
True
True
True
True

通过判断,可以确定我们所知道的常用的数据类型都是可以被迭代的。

使用Iterator模块可以判断对象是否是迭代器:

from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("a.txt")
# 查看是否都是可迭代的
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
#运行结果
False
False
False
False
False
True

可知只有文件是迭代器,所以可以直接使用next(),而不需要转换成迭代器。

什么是生成器

生产器就是一个是带有yield的函数

下面来看一个简单的生成器

def my_yield():
 print('first')
 yield 1
g=my_yield()
print(g)
#运行结果
<generator object my_yield at 0x0000024366D7E258>

生成器也是一个迭代器

from collections import Iterator
def my_yield():
 print('first')
 yield 1
g=my_yield()
print(isinstance(g,Iterator))
#运行结果
True

那就可以用next()来取值了

print(next(g))
#运行结果
first
1

生成器的执行过程

我们来看以下下面这个例子,了解生产的执行流程

def my_yield():
 print('first')
 yield 1
 print('second')
 yield 2
 print('Third')
 yield 3
g=my_yield()
next(g)
next(g)
next(g)
#运行结果
first
second
Third

1.定义生成器my_yield,并将其赋值给了g

def my_yield():
g=my_yield()

2.开始第一次执行next(),开始执行生产器函数 ,打印第一语句,遇到yileld的时候暂停,并返回一个1,如果你想打印返回值的话,这里会显示1

print('first')
 yield 1

3.再执行2次,打印字符串(每执行一次都会暂停一下)

print('second')
 yield 2
 print('Third')
 yield 3

4.如果再加一次next()就会报出StopIteration异常了

生成器在每次暂停的时候,函数的状态将被保存下来,来看下面的例子:

def foo():
 i=0
 while True:
  yield i
  i+=1
g=foo()
for num in g:
 if num < 10:
  print(num)
 else:
  break
#运行结果

for循环中隐含next(),每next一次,暂停一次,if语句判断一次,然后执行下一次next,可以看到我们的while循环并没有无限循环下去,而是状态被保存下来了。

协程函数

我们来看下面这个生成器和执行结果

def eater(name):
 print('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
next(e)
e.send('egg') #给yield送一个值,并继续执行代码
e.send('tomato')
#运行结果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat

send可直接以向yield传值,含有yield表达式的函数我们也称为协程函数,

这运行程序的时候,不可以直接send,必须先使用next()初始化生成器。

如果存在多个这样的函数,那么我们每次执行的时候都要去next()一下,为了防止忘记这一步操作,可以使用装饰器初始化:

def init(func):
 def wrapper(*args):
  res = func(*args)
  next(res)  # 在这里执行next
  return res
 return wrapper
@init
def eater(name):
 print('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
e.send('egg') 
e.send('tomato')

所以在程序中有更多的生成器需要初始化的时候,直接调用这个装饰器就可以了。

以上这篇老生常谈Python之装饰器、迭代器和生成器就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
跟老齐学Python之通过Python连接数据库
Oct 28 Python
Python功能键的读取方法
May 28 Python
深入解析Python中的变量和赋值运算符
Oct 12 Python
利用python实现简单的循环购物车功能示例代码
Jul 05 Python
浅谈Django自定义模板标签template_tags的用处
Dec 20 Python
Python实现的随机森林算法与简单总结
Jan 30 Python
Python实现繁体中文与简体中文相互转换的方法示例
Dec 18 Python
解决Django中checkbox复选框的传值问题
Mar 31 Python
OpenCV+Python3.5 简易手势识别的实现
Dec 21 Python
Python opencv缺陷检测的实现及问题解决
Apr 24 Python
python3操作redis实现List列表实例
Aug 04 Python
Python利用zhdate模块实现农历日期处理
Mar 31 Python
python基础之入门必看操作
Jul 26 #Python
Python简单定义与使用字典dict的方法示例
Jul 25 #Python
Python学习入门之区块链详解
Jul 25 #Python
Python列表list解析操作示例【整数操作、字符操作、矩阵操作】
Jul 25 #Python
Python中的错误和异常处理简单操作示例【try-except用法】
Jul 25 #Python
Python中函数及默认参数的定义与调用操作实例分析
Jul 25 #Python
Python实现类的创建与使用方法示例
Jul 25 #Python
You might like
开启PHP的伪静态模式
2015/12/31 PHP
利用Homestead快速运行一个Laravel项目的方法详解
2017/11/14 PHP
用js+xml自动生成表格的东西
2006/12/21 Javascript
推荐自用 Javascript 缩图函数 (onDOMLoaded)……
2007/10/23 Javascript
jQuery图片播放8款精美插件分享
2013/02/17 Javascript
Javascript 垃圾收集机制介绍理解
2013/05/14 Javascript
JS实现控制表格单元格垂直对齐的方法
2015/03/30 Javascript
关于json字符串与实体之间的严格验证代码
2016/11/10 Javascript
jQuery与JavaScript节点创建方法的对比
2016/11/18 Javascript
如何防止INPUT按回车自动提交表单FORM
2016/12/06 Javascript
100多个基础常用JS函数和语法集合大全
2017/02/16 Javascript
微信小程序实现卡片左右滑动效果的示例代码
2019/05/01 Javascript
Vant 中的Toast设置全局的延迟时间操作
2020/11/04 Javascript
[02:07]DOTA2新英雄展现中国元素,完美“圣典”亮相央视
2016/12/19 DOTA
使用Node.js和Socket.IO扩展Django的实时处理功能
2015/04/20 Python
PyQt5每天必学之关闭窗口
2018/04/19 Python
解决matplotlib库show()方法不显示图片的问题
2018/05/24 Python
详解python如何在django中为用户模型添加自定义权限
2018/10/15 Python
python命令行参数用法实例分析
2019/06/25 Python
在PyCharm的 Terminal(终端)切换Python版本的方法
2019/08/02 Python
Python面向对象之多态原理与用法案例分析
2019/12/30 Python
在django admin详情表单显示中添加自定义控件的实现
2020/03/11 Python
python爬虫线程池案例详解(梨视频短视频爬取)
2021/02/20 Python
法国亚马逊官方网站:Amazon.fr
2020/12/19 全球购物
JAVA软件工程师测试题
2014/07/25 面试题
试用期转正鉴定评语
2014/01/27 职场文书
小学优秀教育工作者事迹材料
2014/05/09 职场文书
中国梦演讲稿开场白
2014/08/28 职场文书
群众路线学习笔记范文
2014/11/06 职场文书
2014年社区工作总结
2014/11/18 职场文书
廉洁自律承诺书范文
2015/04/28 职场文书
《棉鞋里的阳光》教学反思
2016/02/20 职场文书
Spring Cloud 中@FeignClient注解中的contextId属性详解
2021/09/25 Java/Android
Python编程根据字典列表相同键的值进行合并
2021/10/05 Python
如何在python中实现ECDSA你知道吗
2021/11/23 Python
十大最强水系宝可梦,最美宝可梦排第三,榜首大家最熟悉
2022/03/18 日漫