一、核心要义
主要解释函数装饰器的工作原理,包括最简单的注册装饰器和较复杂的参数化装饰器。同时,因为装饰器的实现依赖于闭包,因此会首先介绍闭包存在的原因和工作原理。
二、代码示例
1、变量作用域规则
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 11:26
# @Author : Maple
# @File : 01-变量作用域规则.py
# @Software: PyCharm
b = 10
def f1(a):
print(a)
print(b)
# b = 20 # 在函数体中给b赋值,因此会被判断为局部变量
def f1_revise(a):
global b # 声明函数体中的b为全局变量
print(a)
print(b)
b = 20
"""列表的作用域"""
students = []
def f2():
# students指向全局变量
students.append('a')
return id(students)
def f3():
# 内部再声明一个 students,其为局部变量,与外部的students不是同一个对象
students = []
students.append('a')
return id(students)
def f4():
# 声明全局变量
global students
students +=[1]
# print(id(students))
return id(students)
if __name__ == '__main__':
# 1. f1测试
# 说明: 1.Python在编译函数f1的定义体时,判断b为局部变量,因为在函数体中给b赋值了
# 2.所以在调用函数f1(10)的时候,执行到print(b),发现局部变量b还没有赋值,此时就会报错
#f1(10) # UnboundLocalError: local variable 'b' referenced before assignment
# 2. f1_revise测试
f1_revise(20) # 20,10
# 全局变量b的值被修改
print(b) # 20
# 3.f2测试
print(f2() == (id(students))) #True
# 4.f3测试
print(f3() == (id(students))) #False
# 5.f4测试
print(f4() == (id(students))) #True
2、闭包
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 11:35
# @Author : Maple
# @File : 02-闭包.py
# @Software: PyCharm
"""需求:计算移动平局值
每调用一次函数,传入一个新的数值,然后和前面的所有值进行累加,再计算最后的平均值
"""
# 1. 借用数组方式实现
def make_avg():
num_list = []
def avg(new_value):
num_list.append(new_value)
total = sum(num_list)
return total / len(num_list)
return avg
# 2.直接使用变量方式实现:但存在一个坑
def make_avg_revise1():
count = 0
total = 0
def avg(new_value):
count += 1
total += new_value
return total / count
return avg
# 3.直接使用变量方式实现:填坑
def make_avg_revise2():
count = 0
total = 0
def avg(new_value):
nonlocal count,total # 通过nonlocal 将变量标记为`自由变量`
count += 1
total += new_value
return total / count
return avg
if __name__ == '__main__':
print("***1. make_avg 测试**********************")
# 1. make_avg 测试
avg = make_avg()
# 分析:1.按理说调用完 make_avg_revise1()返回avg1后,make_avg_revise1函数中的局部变量num_list的作用域应该消失了
# 2.但实际上,在avg1中仍然能够调用num_list,这就是所谓闭包现象(变量的作用域外延了)
# 3.num_list被称作`自由变量`
print(avg(5)) # 5.0
print(avg(10)) # 7.5
# 查看avg1 创建和绑定的变量
## 1-1 创建的局部变量
print(avg.__code__.co_varnames) # ('new_value', 'total')
## 1-2 绑定的自由变量
print(avg.__code__.co_freevars) # ('num_list',)
## 自由变量num_list绑定在avg1的closure属性中:是一个cell对象
print(avg.__closure__) # (<cell at 0x000001C6095CA760: list object at 0x000001C6095C0900>,)
## num_list的值则在cell对象中的cell_contents属性中
print(avg.__closure__[0].cell_contents) # [5, 10]
print("*** 2.调用make_avg_revise1会报错**********************")
# 2.调用make_avg_revise1会报错
# 分析:1. 内层函数avg的变量count 和 total在函数内部赋值,因为在函数体编译的时候,会被当作局部变量,但是又没有初始化声明,所以
# 当函数函数调用的时候,会报错
try:
avg = make_avg_revise1()
print(avg(5))
except Exception as e:
print(e) #local variable 'count' referenced before assignment
print("*** 3.make_avg_revise2测试**********************")
# 3.make_avg_revise2测试
avg2 = make_avg_revise2()
print(avg2(5)) # 5.0
print(avg2(10)) # 7.5
3、装饰器
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 16:04
# @Author : Maple
# @File : 03-装饰器.py
# @Software: PyCharm
def decoration(fun):
def inner(*args):
print("do something before decorated function being excuted")
result = fun(*args)
print("do something after decorated function being excuted")
return result
return inner
@decoration
def add(a,b):
return a + b
if __name__ == '__main__':
f = add
# f已经变成 inner,之后调用inner本质上是在调用inner函数
print(f) # <function decoration.<locals>.inner at 0x0000015424D75310>
# f调用过程与闭包有什么关系?
# f = add 等价于 f = decoration(add),此后再调用f, 外层函数参数add的作用域应该已经"消失"
# 但由于闭包原理,add作为`自由变量`,仍然会被绑定在f中
result = f(1,2)
"""
do something before decorated function being excuted
do something after decorated function being excuted
"""
print(result) # 3
4、装饰器应用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 16:18
# @Author : Maple
# @File : 04-装饰器应用.py
# @Software: PyCharm
"""利用装饰器对上一章中最优策略 部分进行改写"""
from collections import namedtuple
# 顾客具名元组
Customer = namedtuple('Customer','name fidelity')
# 定义商品类
class Item:
def __init__(self,product,quantity,price):
"""
:param product: 商品名称
:param quantity: 商品数量
:param price: 商品单价
"""
self.product = product
self.quantity = quantity
self.price = price
def totol(self):
"""
:return:订单总费用
"""
return self.quantity * self.price
# 定义上下文(订单类)
class Order:
def __init__(self,customer,cart,promotion=None):
"""
:param customer: 顾客
:param cart: 购物车
:param promotion: 优惠策略
"""
self.customer = customer
self.cart = cart
self.promotion = promotion
def total(self):
"""
:return:顾客订单打折之前的总费用
"""
if not hasattr(self,'__total'):
self.__total = sum(item.totol() for item in self.cart)
return self.__total
def due(self):
"""
:return:顾客最终支付的费用
"""
if self.promotion is None:
return self.total()
return self.total() - self.promotion(self)
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
# 策略数组
promos = []
def promotion(func):
# 这里的promos指向全局变量,为何不是局部变量?list是可变类型,append操作并不会生成新的对象,
promos.append(func)
return func
# 具体策略1:积分优惠策略
# 被promotion装饰的函数,会被append到策略数组promos中
@promotion
def FidelityPromo(order):
"""如果积分大于1000,享受15%优惠"""
if order.customer.fidelity > 1000:
return order.total() * 0.15
else:
return 0
# 具体策略2
@promotion
def BulkItemPromo(order):
"""单个商品为20个及以上时,提供10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 10:
discount += item.totol()* 0.1
return discount
# 具体策略3
@promotion
def LargetOrderPromo(order):
"""购物车中不同商品种类数量达到3个或以上提供7%折扣"""
discount = 0
# 获取购物车中所有不同的商品
products = {item.product for item in order.cart}
if len(products) >=3:
discount += order.total() * 0.07
return round(discount,2)
# 最优策略
def optimization_strategy(order):
"""
:param order: 订单类
:return:最优策略和最大折扣
"""
# 手动定义所有优惠策略
p_final = None
discount_final = 0
for p in promos:
discount = p(order)
if discount > discount_final:
discount_final = discount
p_final = p
return (p_final,discount_final)
if __name__ == '__main__':
# 1. 最优策略示例1
cus1 = Customer('Maple', 2000) # 用户积分大于1000,享受15%(注意:为了测试,数值从5%调整到15%)优惠
cart1 = [Item('banana', 20, 2.0), Item('apple', 10, 1.0)]
o1 = Order(cus1, cart1, FidelityPromo)
print(optimization_strategy(o1)) # (<function FidelityPromo at 0x0000021CAD565310>, 7.5)
print('=====================================================')
# 2. 最优策略示例2
cus2 = Customer('Jack', 880)
cart2 = [Item('Book', 30, 1.0), Item('Radio', 5, 1.0)] # Book订单超过20个,提供10%折扣
o2 = Order(cus2, cart2, BulkItemPromo)
print(optimization_strategy(o2)) # (<function BulkItemPromo at 0x0000021CAD5653A0>, 3.0)
print('=====================================================')
# 3. 最优策略示例3
cus3 = Customer('Mick', 300)
cart3 = [Item('Phone', 5, 2.0), Item('Water', 5, 1.0), Item('ring', 8, 2)] # 购物车不同商品达到3个.提供7%折扣
o3 = Order(cus3, cart3, LargetOrderPromo)
print(optimization_strategy(o3)) # (<function LargetOrderPromo at 0x0000021CAD565430>, 2.17)
5、clock_deco
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/2 20:20
# @Author : Maple
# @File : 05-clock_deco.py
# @Software: PyCharm
import time
from functools import reduce
from operator import mul
def clock(func):
"""clock装饰器"""
def clocked(*args):
start = time.perf_counter()
result = func(*args)
end = time.perf_counter()
time_takes = end = start
arg_str = ','.join([repr(arg) for arg in args])
print('[%0.8fs] %s(%s) -> %s' % (time_takes,func.__name__,arg_str,result))
return result
return clocked
@clock
def f1(n):
return reduce(mul,range(1,n+1))
if __name__ == '__main__':
# clock装饰器测试
print(f1(5))
"""[0.02694280s] f1(5) -> 120
120
"""
6、使用functools.lur_cache做缓存
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/2 20:56
# @Author : Maple
# @File : 06-使用functools.lur_cache做备忘.py
# @Software: PyCharm
from clock_deco import clock
import functools
# 如果不使用lur_cache
# @clock
# def fibonacci(n):
# if n < 2:
# return n
# return fibonacci(n-2) + fibonacci(n-1)
# 使用lru_cache
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__ == '__main__':
#1.原生fibonacci测试
"""打印结果
[0.02825890s] fibonacci(0) -> 0
[0.02828490s] fibonacci(1) -> 1
[0.02825830s] fibonacci(2) -> 1
[0.02829540s] fibonacci(1) -> 1
[0.02830080s] fibonacci(0) -> 0
[0.02830610s] fibonacci(1) -> 1
[0.02830040s] fibonacci(2) -> 1
[0.02829520s] fibonacci(3) -> 2
[0.02825730s] fibonacci(4) -> 3
3
"""
# print(fibonacci(4))
#2.使用lru_cache测试
"""打印结果
[0.02470590s] fibonacci(0) -> 0
[0.02472680s] fibonacci(1) -> 1
[0.02470550s] fibonacci(2) -> 1
[0.02473770s] fibonacci(3) -> 2
[0.02470470s] fibonacci(4) -> 3
结果说明:
(1)n的每个值都只调用一次
(2)这是因为fibonacci(n)的值会被缓存起来,下次用到的时候可以直接从缓存获取结果,而不用再重新计算
"""
print(fibonacci(4))
补充说明原生fibonacci测试结果
7、单分派泛函数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 9:56
# @Author : Maple
# @File : 07-单分派泛函数-1.py
# @Software: PyCharm
from collections import abc
import html
import numbers
from functools import singledispatch
def html_parse(obj):
"""生成Html,返回不同类型的Python对象
:param obj: Python对象
:return: html
"""
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@singledispatch
def html_parse_enhance(obj):
"""
针对不同的Python对象,以自定义的方式显示
1. str: 把内部的换行符替换为'<br>\n';不适用<pre>,而是使用<p>
2. int: 以十进制和十六进制显示数字,示例: <pre>42 (0x2a)</pre>
3. list:输出一个html列表,根据各个元素的类型进行格式化。示例:html_parse_enhance(['maple',42,{1,23}]),输出->
<ul>
<li><p>maple</p><li>
<li><pre>42 (0x2a)</pre><li>
<li><pre>{123}</pre><li>
</ur>
:param obj:Python对象
:return:html
"""
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@html_parse_enhance.register(str)
def _(text):
"""对于str类型:把内部的换行符替换为'<br>\n';不适用<pre>,而是使用<p>"""
content = html.escape(text).replace('\n','<br>\n')
return '<p>{}</p>'.format(content)
@html_parse_enhance.register(numbers.Integral)
def _(n):
"""对于整数类型: 以十进制和十六进制显示数字,示例: <pre>42 (0x2a)</pre>"""
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@html_parse_enhance.register(tuple)
@html_parse_enhance.register(abc.MutableSequence)
def _(seq):
"""对于list类型: 输出一个html列表,根据各个元素的类型进行格式化"""
content = '</li>\n<li>'.join([html_parse_enhance(obj) for obj in seq])
return '<ul>\n<li>{}</li>\n</ul>'.format(content)
if __name__ == '__main__':
# 1.集合对象测试
r1 = html_parse({1,2,3})
print(r1) # <pre>{1, 2, 3}</pre>
r1_eh = html_parse_enhance({1,2,3})
print(r1_eh) #<pre>{1, 2, 3}</pre>
# 2.函数对象测试
print('******2.函数对象测试*******************')
r2 = html_parse(abs)
print(r2) # <pre><built-in function abs></pre>
r2_eh = html_parse_enhance(abs)
print(r2_eh) #<pre><built-in function abs></pre>
# 3.包含换行符\n的字符串测试
print('******3.包含换行符\n的字符串测试*******************')
r3 = html_parse('maple \n abc')
print(r3) # <pre>'maple \n abc'</pre>
r3_eh = html_parse_enhance('maple \n abc')
"""<p>maple <br>
abc</p>
"""
print(r3_eh)
# 4.整数测试
print('******4.整数测试*******************')
r4 = html_parse(42)
print(r4) # <pre>42</pre>
r4_eh = html_parse_enhance(42) # <pre>42 (0x2a)</pre>
print(r4_eh)
# 5.列表对象测试
print('******5.列表对象测试*******************')
r5 = html_parse(['maple',33,{1,2,3}])
print(r5) # <pre>[1, 2, 3]</pre>
r5_eh = html_parse_enhance(['maple',33,{1,2,3}])
"""<ul>
<li><p>maple</p></li>
<li><pre>33 (0x21)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>
"""
print(r5_eh)
8、装饰器工厂函数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 16:47
# @Author : Maple
# @File : 08-装饰器工厂函数.py
# @Software: PyCharm
import time
from functools import reduce
from operator import mul
registry = set()
# 定义一个装饰器工厂函数:注册或取消被装饰函数
def register(active=True):
def decorate(func):
print('running register(active = %s) ——> decorate(%s)' %(active,func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
# 注意:装饰器工厂函数 并不是装饰器,必须作为函数调用,即后面要加(),即使不传参数
# 在f1上加上@register()后,模块加载的时候就会自动执行register里面的代码了
@register()
def f1(n):
return reduce(mul,range(1,n+1))
@register(active=False)
def f2():
pass
def f3():
pass
if __name__ == '__main__':
# 1. 模块加载的时候,就会执行register里面的代码
# 因此会输出:
"""
running register(active = True) ——> decorate(<function f1 at 0x000001D5F61C4F70>)
running register(active = False) ——> decorate(<function f2 at 0x000001D5F61CC430>)
"""
# 2.查看registry的值: 当前只有函数f1注册了
print(registry) # {<function f1 at 0x00000141DCFAC0D0>}
# 3.f3上并没有加@register,如何手动注册呢?
register(active=True)(f3) # running register(active = True) ——> decorate(<function f3 at 0x00000287ECF45790>)
# 再次查看registry:f3也被注册
print(registry) # {<function f3 at 0x00000287ECF45790>, <function f1 at 0x00000287ED00C040>}
9、参数化clock装饰器
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/3 17:16
# @Author : Maple
# @File : 09-参数化clock装饰器.py
# @Software: PyCharm
import time
from functools import reduce
from operator import mul
DEFAULT_FOTMAT = '[{time_takes:0.8f}s] {name}({arg_str}) -> {result}'
def clock(fmt = DEFAULT_FOTMAT):
def decorate(func):
"""clock装饰器"""
def clocked(*args):
start = time.perf_counter()
result = func(*args)
end = time.perf_counter()
time_takes = end - start
name = func.__name__
arg_str = ','.join([repr(arg) for arg in args])
# *locals是获取clocked中的局部变量:name,arg_str等
print(fmt.format(**locals()))
return result
return clocked
return decorate
@clock()
def f1(n):
return reduce(mul,range(1,n+1))
@clock('{name}({arg_str}): {result}')
def f2(n):
return reduce(mul,range(1,n+1))
@clock('fun_name:{name};time_takes:{time_takes}')
def f3(n):
return reduce(mul,range(1,n+1))
if __name__ == '__main__':
# f1指向clocked函数
f = f1
print(f) #<function clock.<locals>.decorate.<locals>.clocked at 0x000001DD7193C4C0>
# 1. 默认格式测试
f1(4) # [0.00000460s] f1(4) -> 24
#2.自定义格式1测试
f2(4) # f2(4): 24
#3.自定义格式3测试
f3(4) # fun_name:f3;time_takes:8.000000000021878e-07