循序渐进Python3(四) -- 装饰器、迭代器和生成器
初识装饰器(decorator )
Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。
先看一个例子:
#!/usr/bin/env python def outer(func):
print('这是outer。') def inner():
print('这是inner。')
func()
print('这是最后。')
return inner @outer
def f1():
print('这是F1。') f1()
输出结果:
这是outer。
这是inner。
这是F1。
这是最后。
是不是对这个输出感到惊奇?下面详细说说这段代码的执行过程。
- 代码从上到下执行,先将outer函数和f1函数载入内存,然后调用f1()
- 在f1函数前一行加@outer表示调用outer装饰器
- 执行f1函数时,将函数名f1当作参数传入装饰器outer,并执行outer
- 装饰器outer的参数func其实就是函数f1,而outer的返回值inner将赋值给f1,所以“rerurn inner”这句代码必不可少,而原来的f1将被inner取代
- 执行装饰器outer先输出
“这是outer。”
,下面在执行函数inner(inner其实就是新的f1),输出"这是inner。" , 然后执行func函数,func实际上就是上一步传入的f1
,所以执行func()就是执行原函数f1(),所以输出”这是F1。“, 然后再输出"这是最后。"
此执行过程需不断体会方能领悟。
如果暂时无法领悟,可以理解为装饰器就是给函数做了一个装饰,让这个函数在执行之前或者执行之后或者执行前后可以做一些其他事情。
下面对刚才的代码略作修改,使之成为一个带参数的函数。
#!/usr/bin/env python def outer(func):
print('这是outer。') def inner(y):
print('这是inner。')
r = func(y)
print('这是最后。')
return r
return inner @outer
def f1(x):
x += 100
print('这是F1。')
return x a = f1(10)
print(a) @outer
def f2(x):
x -= 100
print('这是F2')
return x a = f2(10)
print(a)
输出结果:
这是outer。
这是inner。
这是F1。
这是最后。
110
这是outer。
这是inner。
这是F2。
这是最后。
-90
可以看到我们给f1添加了参数和返回值,那么对应的,装饰器里的inner也需要添加参数和返回值。
并且我们添加了函数f2,同样可以只用装饰器outer。
sleep(100000000)
仔细体会之后我们进行下一步,如果函数f2带有2个或多个参数,还能和只带一个参数的f1一起使用装饰器outer吗?
当然可以:
def outer(func):
print('这是outer。') def inner(*args,**kwargs):
print('这是inner。')
r = func(*args,**kwargs)
print('这是最后。')
return r
return inner @outer
def f1(x):
x += 100
print('这是F1。')
return x a = f1(10)
print(a) @outer
def f2(x,y,z):
a = x+y+z
print('这是F2。')
return a a = f2(10,20,30)
print(a)
输出结果:
这是outer。
这是inner。
这是F1。
这是最后。
110
这是outer。
这是inner。
这是F2。
这是最后。
60
可见f1和f2分别带有不同数量的参数,但是装饰器outer都可以起到作用。其关键是万能参数:”*args,**kwargs“
那么,同一个函数可以有多个装饰器吗?
答案是:不能
哈哈,不能是不可能的。请看:
#!/usr/bin/env python def outer1(func): def inner(*args,**kwargs):
print('这是装饰器1的inner。')
r = func(*args,**kwargs)
print('这是装饰器1的结束。')
return r
return inner def outer2(func): def inner(*args,**kwargs):
print('这是装饰器2的inner。')
r = func(*args,**kwargs)
print('这是装饰器2的结束。')
return r
return inner @outer2
@outer1
def f1(x):
x += 100
print('这是F1。')
return x a = f1(10)
print(a)
输出如下:
这是装饰器2的inner。
这是装饰器1的inner。
这是F1。
这是装饰器1的结束。
这是装饰器2的结束。
110
我们将两个装饰器的顺序颠倒:
#!/usr/bin/env python def outer1(func): def inner(*args,**kwargs):
print('这是装饰器1的inner。')
r = func(*args,**kwargs)
print('这是装饰器1的结束。')
return r
return inner def outer2(func): def inner(*args,**kwargs):
print('这是装饰器2的inner。')
r = func(*args,**kwargs)
print('这是装饰器2的结束。')
return r
return inner @outer1
@outer2
def f1(x):
x += 100
print('这是F1。')
return x a = f1(10)
print(a)
输出如下:
这是装饰器1的inner。
这是装饰器2的inner。
这是F1。
这是装饰器2的结束。
这是装饰器1的结束。
110
看到没?两个装饰器就相当于在外面又套了一层,多了一层装饰而已。
结论:要想你的代码看起来逼格满满,多整点装饰。。。。。。。。。。。。
最后:
还有什么更吊的装饰器吗?
#!/usr/bin/env python
# Version = 3.5.2
__auth__ = '无名小妖' def fout(a1, a2): def outer(main_func): def wrapper(request, kargs):
print(a1())
main_result = main_func(request, kargs)
print(a2())
return main_result
return wrapper
return outer def f1():
a1 = 100
return a1 def f2():
a2 = 200
return a2 @fout(f1,f2)
def Index(request,kargs):
print(request,kargs) Index(20,30)
输出如下:
100
20 30
200
'''
1、第一步:把def fout(a1,a2): 加载到内存
2、第二步:@fout(f1,f2) ==》 调用了装饰器 ==》 return outer 然后返回给函数Index
3、第三步:执行outer函数并 Index ==》 wrapper
4、执行wrapper 函数,这样做的意义就是除了原函数给的参数外,装饰器也可以调用自己定义的参数
'''
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。
使用迭代器的优点
对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
迭代器有两个基本的方法:
(1)__next__ 返回容器的下一个项目
(2)__iter__ 返回迭代器本身
下面用生成斐波那契数列为例子,说明为何用迭代器
代码1
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1
直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。
代码2
def fab(max):
L = []
n, a, b = 0, 0, 1
while n < max:
L.append(b)
a, b = b, a + b
n = n + 1
return L
代码2满足了可复用性的需求,但是占用了内存空间,最好不要。
代码3
对比
for i in range(1000): pass
for i in xrange(1000): pass
前一个返回1000个元素的列表,而后一个在每次迭代中返回一个元素,因此可以使用迭代器来解决复用可占空间的问题
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1 def __iter__(self):
return self def next(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
执行
1
2
3
4
5
6
7
8
9
|
>>> for key in Fabs( 5 ): print key 1 1 2 3 5 |
Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数 。
另外,我们之前提到过,使用内置函数iter()可以获取迭代器对象:
>>> lst = range(5)
>>> it = iter(lst)
>>> it
<range_iterator object at 0x001BC518>
使用__next__()方法可以访问下一个元素:
>>> it.__next__()
0
>>> it.__next__()
1
>>> it.__next__()
2
使用__iter__()返回迭代器本身:
>>> it.__iter__()
<range_iterator object at 0x001BC518>
初识生成器(generator)
带有 yield 的函数在 Python 中被称之为 generator(生成器),几个例子说明下(还是用生成斐波那契数列说明)
可以看出代码3远没有代码1简洁,生成器(yield)既可以保持代码1的简洁性,又可以保持代码3的效果。
代码4
>>> def fab(max):
... n, a, b = 0, 0, 1
... while n < max:
... yield b
... a, b = b, a + b
... n = n + 1
...
>>> for n in fab(5):
... print(n)
...
1
1
2
3
5
>>>
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
return作用
在一个生成器中,如果没有return,则默认执行到函数完毕;如果遇到return,则直接抛出 StopIteration 终止迭代。
代码5 文件读取
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
循序渐进Python3(四) -- 装饰器、迭代器和生成器的更多相关文章
- 第四天 内置函数2 随机码 装饰器 迭代器、生成器 递归 冒泡算法 JSON
关于函数的return li = [11,22,33,44] def f1(arg): arg.append(55) li = f1(li) print(li) 因为li = f1(li) 实际赋值的 ...
- Day4 装饰器——迭代器——生成器
一 装饰器 1.1 函数对象 一 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二 利用该特性,优雅的取代多 ...
- (转)Python3.5——装饰器及应用详解
原文:https://blog.csdn.net/loveliuzz/article/details/77853346 Python3.5——装饰器及应用详解(下)----https://blog.c ...
- python3.7 装饰器
#!/usr/bin/env python __author__ = "lrtao2010" #python3.7 装饰器 #装饰器 ''' 定义:本质就是一个函数,作用是为其他函 ...
- s14 第4天 关于python3.0编码 函数式编程 装饰器 列表生成式 生成器 内置方法
python3 编码默认为unicode,unicode和utf-8都是默认支持中文的. 如果要python3的编码改为utf-8,则或者在一开始就声明全局使用utf-8 #_*_coding:utf ...
- day04 装饰器 迭代器&生成器 Json & pickle 数据序列化 内置函数
回顾下上次的内容 转码过程: 先decode 为 Unicode(万国码 ) 然后encode 成需要的格式 3.0 默认是Unicode 不是UTF-8 所以不需要指定 如果非要转为U ...
- day4装饰器-迭代器&&生成器
一.装饰器 定义:本质是函数,(装饰其他函数)就是为其它函数添加附加功能 原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 实现装饰器知识储备: 1.函数及“变量” 2.高阶 ...
- python_装饰器——迭代器——生成器
一.装饰器 1.什么是装饰器? 器=>工具,装饰=>增加功能 1.不修改源代码 2.不修改调用方式 装饰器是在遵循1和2原则的基础上为被装饰对象增加功能的工具 2.实现无参装饰器 1.无参 ...
- python装饰器,迭代器,生成器,协程
python装饰器[1] 首先先明白以下两点 #嵌套函数 def out1(): def inner1(): print(1234) inner1()#当没有加入inner时out()不会打印输出12 ...
- python笔记3 闭包 装饰器 迭代器 生成器 内置函数 初识递归 列表推导式 字典推导式
闭包 1, 闭包是嵌套在函数中的 2, 闭包是内层函数对外层函数的变量(非全局变量)的引用(改变) 3,闭包需要将其作为一个对象返回,而且必须逐层返回,直至最外层函数的返回值 闭包例子: def a1 ...
随机推荐
- JSONObject转换JSON--将Date转换为指定格式
项目中,经常会用JSONObject插件将JavaBean或List<JavaBean>转换为JSON格式的字符串,而JavaBean的属性有时候会有java.util.Date这个类型的 ...
- 面试题一 链表中倒数第k个结点
void findLastK(LinkedNode head, int k, int n){ if (head == NULL || k == 0) return; LinkedNode t1 = h ...
- java——XML与java对象装换
-------------------------------Dog类--------------- publicclass Dog implements Serializable{ privates ...
- .net framework 4.0 从 GAC 卸载 程序集
.net framework 4.0 的 GAC 目录: C:\Windows\Microsoft.NET\assembly\GAC_MSIL 要卸载,仍然使用 gacutil 命令,不要带扩展名: ...
- unity, scene视图查看场景时应调成正交模式
scene视图查看场景时应调成正交模式,以避免稍微滑动滚轮就导致视角过远或过近.
- Alpha版本——Postmortem会议
No Bug 031402401鲍亮 031402402曹鑫杰 031402403常松 031402412林淋 031402418汪培侨 031402426许秋鑫 设想和目标 1.我们的软件要解决什么 ...
- grunt集成自动启动
Grunt可以执行像压缩, 编译, 单元测试, 代码检查以及打包发布的任务. 本文介绍使用Grunt实现nodejs程序自启动功能. 目录: Grunt介绍 Grunt安装 Grunt使用 Grunt ...
- VC运行库合集2005/2008/2010/2012/2013/2015
VC运行库合集2005/2008/2010/2012/2013/2015,批处理一键安装(需管理员权限) 链接:http://pan.baidu.com/s/1bpLZoFt 密码:bnk8
- CSAPP(前言)
很久之前就听过有过CSAPP这本书的传闻了,今天终于决定上手这本神作:既然是神作,就要仔细拜读一下,今天看了一下前言部分还真的令人耳目一新,单单是前言部分就让我学习到几个新的知识点: 1.c和Java ...
- 用C#开发了四天的UWP应用有感
第一个就是异步方法,async-await,所谓async关键字,并没有什么实际上的语法意义,只是写在函数签名的位置让编译器方便进行查找以及静态检查,并且提醒程序员这是一个异步方法而已.至于await ...