您的位置:首页 > 编程语言 > Python开发

循序渐进Python3(四) -- 装饰器、迭代器和生成器

2016-08-07 12:12 120 查看
[b]初识装饰器(decorator )[/b]

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

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()


执行

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,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: