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

初识装饰器(decorator )

Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。

使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。

先看一个例子:

循序渐进Python3(四) --   装饰器、迭代器和生成器
#!/usr/bin/env python

def outer(func):
print('这是outer。') def inner():
print('这是inner。')
func()
print('这是最后。')
return inner @outer
def f1():
print('这是F1。') f1()
循序渐进Python3(四) --   装饰器、迭代器和生成器

输出结果:

这是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“

那么,同一个函数可以有多个装饰器吗?

答案是:不能

哈哈,不能是不可能的。请看:

循序渐进Python3(四) --   装饰器、迭代器和生成器
#!/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)
循序渐进Python3(四) --   装饰器、迭代器和生成器

输出如下:

这是装饰器2的inner。
这是装饰器1的inner。
这是F1。
这是装饰器1的结束。
这是装饰器2的结束。
110

我们将两个装饰器的顺序颠倒:

循序渐进Python3(四) --   装饰器、迭代器和生成器
#!/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)
循序渐进Python3(四) --   装饰器、迭代器和生成器

输出如下:

这是装饰器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 函数,这样做的意义就是除了原函数给的参数外,装饰器也可以调用自己定义的参数
 
'''
 
初识迭代器(iterator)

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。

   使用迭代器的优点

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。

迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。

迭代器有两个基本的方法:

  (1)__next__ 返回容器的下一个项目

  (2)__iter__ 返回迭代器本身

下面用生成斐波那契数列为例子,说明为何用迭代器

代码1

循序渐进Python3(四) --   装饰器、迭代器和生成器
 def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1
循序渐进Python3(四) --   装饰器、迭代器和生成器

直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。

代码2

循序渐进Python3(四) --   装饰器、迭代器和生成器
 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
循序渐进Python3(四) --   装饰器、迭代器和生成器

代码2满足了可复用性的需求,但是占用了内存空间,最好不要。

代码3

对比

 for i in range(1000): pass
 for i in xrange(1000): pass

前一个返回1000个元素的列表,而后一个在每次迭代中返回一个元素,因此可以使用迭代器来解决复用可占空间的问题

循序渐进Python3(四) --   装饰器、迭代器和生成器
 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()
循序渐进Python3(四) --   装饰器、迭代器和生成器

执行

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,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

上一篇:C++风格写判断某年某月某日是一年的第几天


下一篇:Vue系列:如何将百度地图包装成Vue的组件