装饰器和闭包

装饰器用来在不修改原函数代码的情况下,增强该函数的功能。要想熟练掌握装饰器,必须理解闭包。

闭包粗略的讲就是保存有状态的函数,它除了在装饰器中有作用之外,还是回调式异步编程和函数式编程的基础。所以闭包的重要性可想而知。

装饰器基础知识

装饰器就本质而言依然是一个函数,他接收一个函数作为参数,然后返回它,或者返回另一个函数。

 1 import functools
 2 def deco(func):
 3     #@functools.wraps(func)
 4     def inner():
 5         print("running inner()")
 6     return inner
 7
 8 @deco
 9 def target():
10     print("running target()")
11 """
12 target = deco(target) 与
13 @deco
14 def target():
15     ...
16 是一样的,@只是python的语法糖而已
17 """
18 target()
19 print(target)

使用python的@语法,虽然函数名称还是target,但是函数指针已经发生了改变,不再指向自身,而是指向deco里面的inner函数。

print(target)打印的结果是:<function deco.<locals>.inner at 0x0000000003590378>

@functools.wraps(func)的作用是把inner的函数名__name__替换成target

加上之后print(target)打印的结果就是:<function target at 0x0000000003590378>.

使用装饰器改进策略模式

 1 promos = []
 2
 3 #装饰器
 4 def deco(func):
 5     promos.append(func)
 6     return func
 7
 8 @deco
 9 def fidelity_promotion(order):
10     """
11     为积分1000及以上的顾客提供5%的折扣
12     """
13     return order.total()*0.05 if order.customer.fidelity >= 1000 else 0
14
15 @deco
16 def bulkitem_promotion(order):
17     """
18     单个商品为20个及以上提供10%的折扣
19     """
20     discount = 0
21     for item in order.cart:
22         if item.quantity >= 20:
23             discount += item.total()*0.1
24     return discount
25
26 @deco
27 def largeorder_promotion(order):
28     """
29     订单中的不同商品达到10个及以上时提供7%折扣
30     """
31     itemset = set(item.product for item in order.cart)
32     #print(itemset)
33     return order.total()*0.07 if len(itemset) >= 10 else 0
34
35 #最佳的折扣方案
36 def best_promotion(order):
37     return max(promo(order) for promo in promos)
38
39 print("------------------------")
40 joe = Customer(‘John Doe‘, 0)
41 ann = Customer(‘Ann Smith‘, 1100)
42 cart = [
43     LineItem(‘banana‘, 4, .5),
44     LineItem(‘apple‘, 10, 1.5),
45     LineItem(‘watermellon‘, 5, 5.0)
46 ]
47
48 print(Order(joe, cart, best_promotion))
49 print(Order(ann, cart, best_promotion))

使用装饰器修饰几个promotion函数,装饰器接收函数作为参数,然后把函数名追加到promos列表中,在把袁术原封不动的返回,这样做有几个好处。

* 折扣函数可以随意定义,只有在定义前加上@deco即可加入促销策略

* 方便禁用某种策略,只需注释掉@deco

不过,多数装饰器都是会被装饰的函数。通常,装饰器会在内部定义一个内部函数,然后将他返回,替换原来的函数。使用内部函数的代码几乎都要靠闭包才能正确运行,为了理解闭包,我们先来看一下python的变量作用域。

变量的作用域

先来看一个例子

1 def f(x):
2     print(x)
3     print(y)
4
5 f(3)

毫无疑问,程序会抛出NameError: global name ‘y‘ is not defined。出现错误不奇怪,因为我们没给b赋值。

如果先给全局变量y赋值,在调用f就不会出现任何问题。

1 y = 4
2 def f(x):
3     print(x)
4     print(y)
5
6 f(3)

以为懂了?再来看一个可能会让你吃惊的例子。

1 y = 4
2 def f(x):
3     print(x)
4     print(y)
5     y += 1
6
7 f(3)

运行结果表明,程序会抛出NameError: global name ‘y‘ is not defined。一开始我也觉得很吃惊,我本以为程序会引用全局变量y,打印出4,可事实却不是这样。

python解释器会认为y是局部变量,因为 y+=1,也就相当于y=y+1,给y赋值了,那么python就会在局部作用域找y,发现y没有值,因此会报错。如果想要使用全局变量y,可以使用global关键字。

1 y = 4
2 def f(x):
3     print(x)
4         global y
5     print(y)
6     y += 1
7
8 f(3)
9 print(y) #y的值变为5

闭包

有时,人们会把闭包和函数弄混,这是有历史原因的:在函数内部定义函数的做法并不常见,直到开始使用lambda函数时才会这样做。而且,只有在涉及嵌套函数才会有闭包问题,因此许多人是同时知道这两个概念的。

闭包,也就是延伸了作用域的函数,包括函数体中的引用、不在函数体也不是全局变量的引用。关键是他能访问函数体之外的非全局变量。

学术性的话通常言简意赅、清晰准确,但不容易理解。

假设我们现在有一个avg函数,他要计算平均值,每次都有新的值加进来,因此每次的平均值可能都不一样。我们想,所以得保存之前的历史数据。

用类实现的代码如下:

 1 class Average(object):
 2     def __init__(self):
 3         self.series = []
 4
 5     def __call__(self, val):
 6         self.series.append(val)
 7         return sum(self.series)/len(self.series)
 8
 9 avg = Average()
10 print(avg(1))
11 print(avg(2))
12 print(avg(3))
13 print(avg(4))

但是今天我们要学习的是闭包,所以用高级函数实现如下:

 1 def make_average():
 2     series = []
 3     def average(val):
 4         series.append(val)
 5         return sum(series)/len(series)
 6     return average
 7
 8
 9 avg = make_average()
10 print(avg(1))
11 print(avg(2))
12 print(avg(3))
13 print(avg(4))

函数make_average内部定义了一个空列表series,和一个内部函数average,average接收一个参数,然后把参数放进列表中,计算平均值,make_average最后返回average函数。

现在,我们来比较一下两者实现的差别。

类的方式实现很明显,他把所有的历史数据都放在实例的series列表中,但是高阶函数里的average到哪里去找series列表呢?

注意,series定义在make_average函数中,那么在average函数中,series被称为自由变量(free variable)。指的是未在本地作用域中绑定的变量。

series参数被绑定在average函数的__closure__属性中,它是个列表,每个元素是cell对象,数据保存在cell_contents中。

1 avg = make_average()
2 print(avg(1))
3 print(avg(2))
4 print(avg(3))
5 print(avg(4))
6 print(avg.__closure__)
7 for item in avg.__closure__:
8     print(item.cell_contents)

运行结果如下:

综上,闭包本质就是函数,只是他会保留定义时在函数外部存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用,但是保留的那些自由变量依然可以使用。

nonlocal声明

前面的make_average实现效率并不高,因为每次添加一个值进去,都得重新编列一遍series列表求和,如果我们能绑定历史的求和值跟元素个数,显然效率更高。

方法一:

 1 def make_average():
 2     s = 0
 3     n = 0
 4     def average(val):
 5         n += 1
 6         s += val
 7         return s/n
 8     return average
 9
10 avg = make_average()
11 avg(1)

一运行,咦?怎么报错了呢

别急,我们换种方法试一下:

方法二:

 1 def make_average():
 2     s_n = {‘sum‘:0, ‘n‘:0}
 3     def average(val):
 4         s_n[‘n‘] += 1
 5         s_n[‘sum‘] += val
 6         return s_n[‘sum‘]/s_n[‘n‘]
 7     return average
 8
 9 avg = make_average()
10 print(avg(1))
11 print(avg(2))
12 print(avg(3))
13 print(avg(4))

一运行,终于没问题了。这是为什么?

在一开始,我们讨论过变量作用域的话题,方法一中, s+=val等价于 s = s+val,也就是给变量s赋值,这时,python就不再把s当成自由变量,而是当成局部变量看待,因此会报错,告诉你s是局部变量在没有赋值前被引用。再来看下为什么方法二没问题,方法二定义了一个字典,尽管在average函数里修改字典里键对应的值,但是并没有对字典本身做什么赋值操作,因此s_n绑定的还是自由变量,同理,你换成列表用append的方式也是一样的。

为了解决方法一中存在的问题,python3引入了nonlocal声明。它的作用就是把变量标记为自由变量,明确的告诉python解释器,这个变量是自由变量而非局部变量,即使在给它赋值,那也是自由变量。

正确的代码如下:

 1 def make_average():
 2     s, n = 0, 0
 3     def average(val):
 4         nonlocal s, n #标记为自由变量
 5         s += val
 6         n += 1
 7         return s/n
 8     return average
 9
10
11 avg = make_average()
12 print(avg(1))
13 print(avg(2))
14 print(avg(3))
15 print(avg(4))

原文地址:https://www.cnblogs.com/forwardfeilds/p/10475306.html

时间: 2024-10-25 04:49:21

装饰器和闭包的相关文章

python函数下篇装饰器和闭包,外加作用域

装饰器和闭包的基础概念 装饰器是一种设计模式能实现代码重用,经常用于查日志,性能测试,事务处理等,抽离函数大量不必的功能. 装饰器:1.装饰器本身是一个函数,用于装饰其它函数:2.功能:增强被装饰函数的功能. 装饰器需要遵循的原则 1.不修改被装饰函数的源代码(开放封闭原则) 2.为被装饰函数添加新功能后,不修改被装饰函数的调用方式 装饰器 = 高阶函数+函数嵌套+闭包 高阶函数 1.函数接受的参数是一个函数名 2.函数的返回值是一个函数名 3.只有上述条件满足一个就是高阶函数 def foo(

函数装饰器和闭包(四)

上一章:函数装饰器和闭包(三) 单分派函数 假设我们现在要开发一个函数,这个函数可以传入一个元素,函数要判断元素的类型,再将其打印出来 from collections.abc import MutableSequence, MutableMapping def print_item(item): if isinstance(item, int): print("int:", item) elif isinstance(item, MutableSequence): print(&qu

Python基础课:装饰器和闭包

#4.装饰器 #闭包 def fx(x): x += 1 def fy(y): return x*y return fy def f1(func): #print('f1 runing') def f2(y): print('f2 runing') return func(y) + 1 return f2 def gun(m): print('gun runing') return m*m #f1(gun) #fg = f1(gun) def fun(m): print('f2 runing')

python11 装饰器与闭包

一.装饰器定义 本质:一种函数,为其他函数增加新功能 原则: 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 需要技能:装饰器=高阶函数+函数嵌套+闭包 二.高阶函数 定义:函数接收的参数是一个函数名或者函数的返回值是一个函数名 三.函数嵌套 定义:函数里面嵌套另一个函数 四.闭包 闭包:在一个作用域里放入定义变量,相当于打了一个包 注意:@timmer等同于 cal=timmer(cal) 1.加上返回值,参数 五.解压序列 调换两个数的值 六 给装饰器加参数 让同一个装饰器因为

python学习-42 装饰器 --- 函数闭包1

函数闭包举例: def father(name): print('hello world') def son(): print('儿子说:我的爸爸是%s' % name) def grandfson(): print('孙子说:我的爷爷是%s' % name) grandfson() son() father('小明') 运行结果: hello world 儿子说:我的爸爸是小明 孙子说:我的爷爷是小明 Process finished with exit code 0 函数的包: 就是嵌套里的

Python 函数装饰器和闭包

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica } 装饰器基础知识 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象. p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 假如有个名为 decorate 的装饰器: @decorate def targ

函数装饰器和闭包(一)

装饰器基础知识 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后将它返回,或者将其替换成另一个函数或可调用对象 def deco(func): def inner(): print("running innner()") return inner # <1> @deco def target(): # <2> print("running target()") target() # <3>

python学习-43 装饰器 -- 函数闭包2

函数闭包为函数加上认证功能 1.登陆账号 user_dic ={'username':None,'login':False} def auth_func(func): def wrapper(*args,**kwargs): if user_dic['username'] and user_dic['login']: res = func(*args,**kwargs) return res username = input('账号:').strip() passwd = input('密码:'

Python之面向对象:闭包和装饰器

一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print name return inner res=outter() 把inner的地址赋值给res res() 相当于执行inner()函数.通过闭包,可以把局部变量在外部也可以使用 2.判断是否为闭包 res.func_closure inner()函数就是一个闭包 3.通过闭包,可以把局部变量在外部也可以