详解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使用chardet判断字符编码
May 09 Python
Python访问纯真IP数据库脚本分享
Jun 29 Python
Python使用wxPython实现计算器
Jan 30 Python
对numpy.append()里的axis的用法详解
Jun 28 Python
Python3实现获取图片文字里中文的方法分析
Dec 13 Python
Python3分析处理声音数据的例子
Aug 27 Python
python使用opencv实现马赛克效果示例
Sep 28 Python
python装饰器代替set get方法实例
Dec 19 Python
python实现翻译word表格小程序
Feb 27 Python
用python获取txt文件中关键字的数量
Dec 24 Python
Python环境搭建过程从安装到Hello World
Feb 05 Python
Python图片处理之图片裁剪教程
May 27 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
php视频拍照上传头像功能实现代码分享
2015/10/08 PHP
详解在YII2框架中使用UEditor编辑器发布文章
2018/11/02 PHP
PHP基于timestamp和nonce实现的防止重放攻击方案分析
2019/07/26 PHP
yii 框架实现按天,月,年,自定义时间段统计数据的方法分析
2020/04/04 PHP
超级强大的表单验证
2006/06/26 Javascript
jqPlot jquery的页面图表绘制工具
2009/07/25 Javascript
Javascript实现仿WebQQ界面的“浮云”兼容 IE7以上版本及FF
2011/04/27 Javascript
分享一个自己写的table表格排序js插件(高效简洁)
2011/10/29 Javascript
Jquery网页内滑动缓冲导航的实现代码
2015/04/05 Javascript
js实现拉幕效果的广告代码
2015/09/02 Javascript
JS截取与分割字符串常用技巧总结
2015/11/10 Javascript
jquery读写cookie操作实例分析
2015/12/24 Javascript
AngularJS中实现显示或隐藏动画效果的方式总结
2015/12/31 Javascript
ES6使用let命令更简单的实现块级作用域实例分析
2017/03/31 Javascript
原生JavaScript实现精美的淘宝轮播图效果示例【附demo源码下载】
2017/05/27 Javascript
jQuery序列化后的表单值转换成Json
2017/06/16 jQuery
详解Vue组件之间的数据通信实例
2017/06/17 Javascript
React Component存在的几种形式详解
2018/11/06 Javascript
在Vue项目中使用snapshot测试的具体使用
2019/04/16 Javascript
Layer.js实现表格溢出内容省略号显示,悬停显示全部的方法
2019/09/16 Javascript
Javascript异步执行不按顺序解决方案
2020/04/30 Javascript
Python yield 小结和实例
2014/04/25 Python
Python中的进程分支fork和exec详解
2015/04/11 Python
实例解析Python设计模式编程之桥接模式的运用
2016/03/02 Python
python TF-IDF算法实现文本关键词提取
2019/05/29 Python
python读取excel数据并且画图的实现示例
2021/02/08 Python
意大利婴儿产品网上商店:Mukako
2018/10/14 全球购物
JSP&Servlet技术面试题
2015/05/21 面试题
高三上学期学习自我评价
2014/04/23 职场文书
领导干部四风问题自我剖析材料
2014/09/25 职场文书
学生会工作感言
2015/08/07 职场文书
西部计划志愿者工作总结
2015/08/11 职场文书
Python 内置函数速查表一览
2021/06/02 Python
MySQL里面的子查询的基本使用
2021/08/02 MySQL
logback如何自定义日志存储
2021/08/30 Java/Android
服务器nginx权限被拒绝解决案例
2022/09/23 Servers