老生常谈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编写基于DHT协议的BT资源爬虫
Mar 19 Python
对python添加模块路径的三种方法总结
Oct 16 Python
对python3 Serial 串口助手的接收读取数据方法详解
Jun 12 Python
Python 用matplotlib画以时间日期为x轴的图像
Aug 06 Python
python进程池实现的多进程文件夹copy器完整示例
Nov 27 Python
Python+numpy实现矩阵的行列扩展方式
Nov 29 Python
Python安装依赖(包)模块方法详解
Feb 14 Python
python 已知一个字符,在一个list中找出近似值或相似值实现模糊匹配
Feb 29 Python
Python判断字符串是否为空和null方法实例
Apr 26 Python
python 统计list中各个元素出现的次数的几种方法
Feb 20 Python
解决Django transaction进行事务管理踩过的坑
Apr 24 Python
Django程序的优化技巧
Apr 29 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
输出控制类
2006/10/09 PHP
php下安装配置fckeditor编辑器的方法
2011/03/02 PHP
深入解析Session是否必须依赖Cookie
2013/08/02 PHP
YII实现分页的方法
2014/07/09 PHP
实现PHP+Mysql无限分类的方法汇总
2015/03/02 PHP
PHP数学运算与数据处理实例分析
2016/04/01 PHP
PHP中SERIALIZE和JSON的序列化与反序列化操作区别分析
2016/10/11 PHP
PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】
2017/11/17 PHP
Javascript实现关联数据(Linked Data)查询及注意细节
2013/02/22 Javascript
jquery实现checkbox全选全不选的简单实例
2013/12/31 Javascript
JavaScript模拟实现键盘打字效果
2015/06/29 Javascript
js判断手机访问或者PC的几个例子(常用于手机跳转)
2015/12/15 Javascript
jQuery实现ajax的叠加和停止(终止ajax请求)
2016/08/08 Javascript
浅谈js基本数据类型和typeof
2016/08/09 Javascript
node.js操作mongodb简单示例分享
2017/05/25 Javascript
vue几个常用跨域处理方式介绍
2018/02/07 Javascript
又拍云 Node.js 实现文件上传、删除功能
2018/10/28 Javascript
vue.js实现会动的简历(包含底部导航功能,编辑功能)
2019/04/08 Javascript
三分钟教你用Node做一个微信哄女友(基友)神器(面向小白)
2019/06/21 Javascript
JS document文档的简单操作完整示例
2020/01/13 Javascript
Vue数字输入框组件使用方法详解
2020/02/10 Javascript
[01:53]DOTA2超级联赛专访Zhou 五年职业青春成长
2013/05/29 DOTA
Python中的字典与成员运算符初步探究
2015/10/13 Python
利用Python实现在同一网络中的本地文件共享方法
2018/06/04 Python
Python 一句话生成字母表的方法
2019/01/02 Python
python scp 批量同步文件的实现方法
2019/01/03 Python
Python字符串大小写转换拼接删除空白
2019/09/19 Python
详解pyinstaller selenium python3 chrome打包问题
2019/10/18 Python
全球领先的各类汽车配件零售商:Advance Auto Parts
2016/08/26 全球购物
Servlet的实例是在生命周期什么时候创建的?配置servlet最重要的是什么?
2012/05/30 面试题
保护动物倡议书
2014/04/15 职场文书
人事行政经理岗位职责
2014/06/18 职场文书
英语课前三分钟演讲稿
2014/08/19 职场文书
vue实现拖拽交换位置
2022/04/07 Vue.js
解决spring.thymeleaf.cache=false不起作用的问题
2022/06/10 Java/Android
JS前端可视化canvas动画原理及其推导实现
2022/08/05 Javascript