详解python中的生成器、迭代器、闭包、装饰器


Posted in Python onAugust 22, 2019

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1|1可迭代对象

以直接作用于 for 循环的数据类型有以下几种:

  • 一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等;
  • 一类是 generator ,包括生成器和带 yield 的generator function。

这些可以直接作用于 for 循环的对象统称为可迭代对象: Iterable 。

1|2判断是否可以迭代

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

from collections import Iterable
isinstance([],Iterable)
# True
isinstance({},Iterable)
# True
isinstance(123,Iterable)
# False
isinstance((x for x in range(10)),Iterable)
# True

1|3什么是迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

from collections import Iterator
isinstance([],Iterator)
False
isinstance({},Iterator)
False
isinstance((x for x in range(10)),Iterator) # 
True

生成器都是迭代器。

1|4iter()函数

虽然list 、 tuple 、 dict 、 set 、 str 等是可迭代对象,但他们不是迭代器。可以通过iter()函数把可迭代对象编程迭代器。

isinstance(iter([]),Iterator)
# True
isinstance(iter({}),Iterator)
# True
isinstance(iter("asdf"),Iterator)
# True

1|5总结:

  • 凡是可作用于 for 循环的对象都是 Iterable 类型。
  • 凡是可作用于 next() 函数的对象都是 Iterator 类型。
  • 集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象。

2|0生成器

2|1什么是生成器

我们可以通过列表生成式来创建一个列表,但是收到内存的限制,列表的容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

1|1修改列表推导式创建生成器的方法

最简单的方法是把列表生成式中的 [ ] 改成 ( ) 就好了。

a = [x for x in range(10)]
print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = (x for x in range(10))
print(b) # <generator object <genexpr> at 0x03387DB0>

如何遍历生成器

我们发现生成器不是能直接打印出来的,我们可以通过next()函数来获得生成器的下一个返回值。

生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

**使用next() 或者__next __():**
print(next(b))
# 0
print(next(b))
# 1
print(next(b))
# 2
print(next(b))
# 3
print(next(b))
# 4
print(next(b))
# 5
print(b.__next__())
# 6
print(b.__next__())
# 7
print(b.__next__())
# 8
print(b.__next__())
# 9
print(b.__next__())
# Traceback (most recent call last):
# File "<input>", line 2, in <module>
# StopIteration

那么有什么简单的方法呢?因为生成器是可迭代对象,也可以使用for循环来遍历它,并且不需要关心 StopIteration 异常。

b = (x for x in range(10))
for x in b:
 print(x)
# 0
# 1
# 2
# 3
#...

1|2函数中使用yield创建生成器的方法

如果如果生成器推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。把你要返回的值前面加yield 即可。

使用函数实现上面代码:

def fn():
 for x in range(3):
  yield x
# 遍历函数实现的生成器
f = fn()
print(next(f))
# 0
print(next(f))
# 1
print(next(f))
# 2
print(next(f))
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration

使用生成器实现斐波拉契数列:

def fib(count):
 n = 0
 a,b = 0,1
 while n < count:
  yield b
  a,b = b,a+b
  n += 1
 return "done"
f = fib(5)
print(next(f))
# 1
print(next(f))
# 1
print(next(f))
# 2
print(next(f))
# 3
print(next(f))
# 5
print(next(f))
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration: done

yield执行流程

  • 当执行next(f)时,函数开始执行到yield,yield 右边的变量x作为next()的返回值被返回,此时函数保存当前的运行状态,并暂停执行。
  • 再次调用next(f)时,函数从上次暂停的位置开始继续执行,再次遇到yield时重复上面的操作
  • 直到生成器遍历结束

我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:

for x in fib(5):
 print(x)
# 1
# 1
# 2
# 3
# 5

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

f = fib(5)
while True:
 try:
  print(next(f))
 except StopIteration as e:
  print("生成器返回值:%s"%e.value)
  break
# 1
# 1
# 2
# 3
# 5
# 生成器返回值:done

1|3send方法

def gen():
 i = 0
 while i<3:
  temp = yield i
  print(temp)
  i+=1
g = gen()
print(g.__next__())
# 0
print(g.send(None))
# None
# 1
print(next(g))
# None
# 2
print(g.send("哈哈")
# 哈哈
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration

上面代码可以看出next()、next ()、send(None)是等价的并没有什么区别。

  • send()其实是比他们更高级的,在之前的代码中yield i是没有返回值的即输出为None。
  • 如果修改send()的形参,那么yield i 的返回值就是括号中的形参,在上面的代码中g.send("哈哈")相当于temp = "哈哈",并且g.send("哈哈")的返回值就是变量i。
  • 使用send时要注意,第一次调用生成器对象时,send()不能传参数否则会报错,第一次必须是send(None),或者第一次调
  • 用next()、next ()也可以。

3|0闭包

3|1什么是闭包

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包

def test(number):
 def test_in(number_in):
  print("test_in函数 的number_in=%s"%number_in)
  return number_in+number
 # 返回test_in函数的引用
 return test_in
ret = test(20)
print(ret(100)) # 相当于直接调用test_in函数,并给它传值100
# test_in函数 的number_in=100
# 120
print(ret(200))
# test_in函数 的number_in=200
# 220

3|2闭包的一个例子

在数学中,一次函数:y=kx+b,在一条确定的直线中,它的k、b是不变的。求y时,根据确定的k、b、x来求出。

def line_conf(k, b):
 def line(x):
  return k*x + b
 return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
# 6
# 25

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

4|0装饰器
4|1什么是装饰器

装饰器就是对一个函数进行装饰,给这个函数增加额外的功能。

def logging(func):
 def wrap():
  print("正在打印日志!")
  func()
 return wrap

@logging # 该装饰器为函数增加了打印日志的额外功能,并且之前函数内部代码不会改变。

def login():
 print("张三正在登陆。")
login()
# 正在打印日志!
# 张三正在登陆。
4|2两个装饰器
def makeBold(fn1):
 def wrapped():
  print("----1----")
  return "<b>"+fn1()+"</b>"
 return wrapped

def makeItalic(fn2):
 def wrapped():
  print("----2----")
  return "<i>"+fn2()+"</i>"
 return wrapped

@makeBold
@makeItalic
def f1():
 print("----3----")
 return "hello world"

ret = f1() # 此时f1并不是f1函数,它是makeBold装饰器返回的wrapped函数的引用。
print(ret)
"""
输出结果:
----1----
----2----
----3----
<b><i>hello world</i></b>
"""

调用流程:

  1. 把函数f1的引用传入装饰器makeItalic中的变量fn2,此时fn2指向f1函数。
  2. 把装饰器makeItalic中wrapped函数的引用传入装饰器makeBold的变量fn1,此时fn1指向装饰器makeItalic中的wrapped函数。
  3. ret = f1()表是执行f1所指向的函数,并返回给ret。

4|3装饰器带参数

一般情况下装饰器内部函数的参数都是不定长参数,保证通用性,确保装饰任何函数时都不会出错。

def logging(func):
 def wrap(*args,**kwargs):
  print("正在打印日志!")
  func(*args,**kwargs)
 return wrap

@logging # 该装饰器为函数增加了打印日志的额外功能,并且之前函数内部代码不会改变。

def login(name,dic):
 print("%s正在登陆。"%name)
 print(dic)
login("李四",{"sex":"男"})
# 正在打印日志!
# 李四正在登陆。
# {'sex': '男'}
4|4类装饰器
class Test(object):
 def __init__(self, func):
  print("---初始化---")
  print("func name is %s"%func.__name__)
  self.__func = func
 def __call__(self):
  print("---装饰器中的功能---")
  self.__func()
@Test
def test():
 print("----test---")
test()
"""

输出结果:

---初始化---
func name is test
---装饰器中的功能---
----test---
"""

说明:

  • 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象并且会把test这个函数名当做参数传递到__init__方法中即在__init__方法中的func变量指向了test函数体。
  • test函数相当于指向了用Test创建出来的实例对象。
  • 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法。
  • 为了能够在__call__方法中调用原来test指向的函数体,所以在__init __方法中就需要一个实例属性来保存这个函数体的引用所以才有了self.__func = func这句代码,从而在调用__call __方法中能够调用到test之前的函数体。

总结

以上所述是小编给大家介绍的python中的生成器、迭代器、闭包、装饰器,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python实现线程池代码分享
Jun 21 Python
解决Scrapy安装错误:Microsoft Visual C++ 14.0 is required...
Oct 01 Python
Python安装Numpy和matplotlib的方法(推荐)
Nov 02 Python
python+splinter自动刷新抢票功能
Sep 25 Python
python通过ffmgep从视频中抽帧的方法
Dec 05 Python
如何利用Python分析出微信朋友男女统计图
Jan 25 Python
使用python实现ftp的文件读写方法
Jul 02 Python
python调用webservice接口的实现
Jul 12 Python
树莓派3 搭建 django 服务器的实例
Aug 29 Python
python序列类型种类详解
Feb 26 Python
Python库安装速度过慢解决方案
Jul 14 Python
详解Python GUI编程之PyQt5入门到实战
Dec 10 Python
python支付宝支付示例详解
Aug 22 #Python
关于python3中setup.py小概念解析
Aug 22 #Python
python3 requests库文件上传与下载实现详解
Aug 22 #Python
python3使用print打印带颜色的字符串代码实例
Aug 22 #Python
Python检查 云备份进程是否正常运行代码实例
Aug 22 #Python
浅谈Python 递归算法指归
Aug 22 #Python
python求加权平均值的实例(附纯python写法)
Aug 22 #Python
You might like
新闻分类录入、显示系统
2006/10/09 PHP
PHP下几种删除目录的方法总结
2007/08/19 PHP
php date()日期时间函数详解
2010/05/16 PHP
获取php页面执行时间,数据库读写次数,函数调用次数等(THINKphp)
2013/06/03 PHP
使用cookie实现统计访问者登陆次数
2013/06/08 PHP
php中explode函数用法分析
2014/11/15 PHP
ThinkPHP设置禁止百度等搜索引擎转码(简单实用)
2016/02/15 PHP
php+html5+ajax实现上传图片的方法
2016/05/14 PHP
PHP长连接实现与使用方法详解
2018/02/11 PHP
jQuery 使用手册(三)
2009/09/23 Javascript
基于jquery的让页面控件不可用的实现代码
2010/04/27 Javascript
js过滤数组重复元素的方法
2010/09/05 Javascript
JS小功能(offsetLeft实现图片滚动效果)实例代码
2013/11/28 Javascript
jQuery中removeData()方法用法实例
2014/12/27 Javascript
微信小程序 触控事件详细介绍
2016/10/17 Javascript
在网页中插入百度地图的步骤详解
2016/12/02 Javascript
layui实现三级导航菜单
2019/07/26 Javascript
node使用mysql获取数据库数据中文乱码问题的解决
2019/12/02 Javascript
vue使用lodop打印控件实现浏览器兼容打印的方法
2021/02/07 Vue.js
超简单使用Python换脸实例
2019/03/27 Python
详解Python:面向对象编程
2019/04/10 Python
Django模板Templates使用方法详解
2019/07/19 Python
python selenium登录豆瓣网过程解析
2019/08/10 Python
Python decimal模块使用方法详解
2020/06/08 Python
雅诗兰黛美国官网:Estee Lauder美国
2016/07/21 全球购物
师范生个人推荐信
2013/11/29 职场文书
2014年五一促销活动方案
2014/03/09 职场文书
教师党员一句话承诺
2014/03/28 职场文书
项目经理任命书
2014/06/04 职场文书
祖国在我心中演讲稿600字
2014/09/23 职场文书
汽车4S店销售经理岗位职责
2015/04/02 职场文书
世界名著读书笔记
2015/06/25 职场文书
2016秋季运动会开幕词
2016/03/04 职场文书
2016年公司“3.12”植树节活动总结
2016/03/16 职场文书
教学工作总结范文5篇
2019/08/19 职场文书
用Python远程登陆服务器的步骤
2021/04/16 Python