《流畅的python》之 设计模式, 装饰器
如果合理利用作为一等对象的函数,某些设计模式可以简化,例如“策略模式”
假设商店制定了如下的销售规则:
1. 1000以上积分的用户,每单享受5%折扣
2. 同一订单中,单个商品的数量达到20以上,享受10%折扣
3. 订单中不同商品达到10件以上,7%折扣
4.一个订单一次只能享受一次折扣
from abc import abstractmethod # 装饰器 from abc import ABC from collections import namedtuple Customer = namedtuple('Customer', 'name fidelity') # 顾客结构体 class LineItem: # 单个商品 def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): # 计算商品的总价 return self.quantity * self.price class Order: # 上下文 订单类 def __init__(self, customer, cart, promotion=None): # 包含的信息:客户信息, 购物车, 折扣策略 self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, '__total'): self._total = sum([item.total() for item in self.cart]) return self._total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion.discount(self) # 这里discount的self参数就是order本身 return self.total() - discount def __repr__(self): # 控制输出的格式 fmt = '<order total: {:.2f} due: {:.2f}>' return fmt.format(self.total(), self.due()) # def print_info(self): # 控制输出的格式 # fmt = '<order total: {:.2f} due: {:.2f}>' # print(fmt.format(self.total(), self.due())) class Promotion(ABC): # 策略,抽象基类 @abstractmethod def discount(self, order): # 装饰器 抽象方法 """返回折扣金额""" class FidelityPromo(Promotion): """ 策略 1 为积分超过1000的用户打折 """ def discount(self, order): return order.total()*0.05 if order.customer.fidelity >= 1000 else 0 class BulkItemPromo(Promotion): """ 策略 2 为单个商品超过20件的用户打折 """ def discount(self, order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * 0.01 return discount class LargeOrderPromo(Promotion): """ 策略 3 为不同商品超过10件以上的用户打折 """ def discount(self, order): discount_items = {item.product for item in order.cart} if len(discount_items) >= 10: return order.total() * 0.07 return 0 if __name__ == "__main__": joe = Customer("Joe D", 0) # 无积分顾客 ann = Customer("Ann jane", 1200) # 有积分顾客 cart = [LineItem("banana", 18, 0.5), # 购物车信息 LineItem("apple", 6, 1.5), LineItem("melon", 8, 1.2)] order1 = Order(ann, cart, FidelityPromo()) order2 = Order(joe, cart, BulkItemPromo()) print(order1) print(order2) # order1.print_info() # print(order1)
对上述代码的改进:
直接通过定义函数实现不同的策略:
from collections import namedtuple Customer = namedtuple('Customer', 'name fidelity') # 顾客结构体 class LineItem: # 单个商品 def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): # 计算商品的总价 return self.quantity * self.price class Order: # 上下文 订单类 def __init__(self, customer, cart, promotion=None): # 包含的信息:客户信息, 购物车, 折扣策略 self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, '__total'): self._total = sum([item.total() for item in self.cart]) return self._total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion(self) # 此时折扣直接使用promotion计算,self此时是order return self.total() - discount def __repr__(self): # 控制输出的格式 fmt = '<order total: {:.2f} due: {:.2f}>' return fmt.format(self.total(), self.due()) # def print_info(self): # 控制输出的格式 # fmt = '<order total: {:.2f} due: {:.2f}>' # print(fmt.format(self.total(), self.due())) # 定义各个策略为函数 def fidelity_promo(order): return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 def bulk_item_promo(order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total()*0.1 return discount def large_order_promo(order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * 0.07 return 0 # 最佳折扣方式 def best_promo(order): promos = [fidelity_promo, bulk_item_promo, large_order_promo] # 将函数作为对象,构建函数存储数据结构 return max(prom(order) for prom in promos) # 返回最大的折扣 if __name__ == "__main__": joe = Customer("Joe D", 900) # 无积分顾客 ann = Customer("Ann jane", 1200) # 有积分顾客 cart = [LineItem("banana", 23, 0.5), # 购物车信息 LineItem("apple", 6, 1.5), LineItem("melon", 8, 1.2)] order1 = Order(ann, cart, fidelity_promo) order2 = Order(joe, cart, best_promo) # 在这里使用best_promo()函数得到最大的折扣 print(order1) print(order2)
这里用到了装饰器:
class Promotion(ABC): # 策略,抽象基类 @abstractmethod def discount(self, order): # 装饰器 抽象方法 """返回折扣金额"""
函数装饰器和闭包:
函数装饰器用于在源码中标记函数,以某种方式增强函数的行为:装饰器是可调用的对象,其参数是另一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,将他返回,或者将其替换成另一个函数或者可调用对象。
# 装饰器 @decorate def target(): print("running target") # 等价的写法 def target(): print("running target") target = decorate(target) # 返回的target不一定是原来的target
装饰器通常把函数替换成另一个函数:
def deco(func): def inner(): print("running inner") return inner # 返回inner函数对象 @deco # 装饰器 def target(): print("running target") if __name__ == "__main__": target() # 调用target实际会运行inner
运行结果:
装饰器的两大特性:
1.能把被装饰的函数替换成其他函数
2.装饰器在加载模块时立即执行(被装饰的函数定义之后):
registry = [] def register(func): # define a decorate print("running register{}".format(func)) # 输出被装饰的函数 registry.append(func) return func # decorate必须返回函数 @register def f1(): print("running f1()") @register def f2(): print("running f2()") def f3(): print("running f3()") def main(): print("running main()") print("registry->", registry) f1() f2() f3() if __name__ == "__main__": main()
运行结果:
在import的时候,会执行装饰器,或者在被装饰的函数顶以后,也会执行装饰器
f1(), f2(),f3()只有在main()中明确调用时才会执行
装饰器的用处:
在上面电商折扣的例子中,定义了best_promo()函数,它的作用时返回最大的折扣策略:但是有一个缺陷,如果系统中新加了折扣方案,则需要手动将折扣方案函数的引用添加到折扣方案列表promos中,否则系统会忽略新定义的折扣方案,而且不报错,这就为系统引入了不易察觉的缺陷:】
解决方案:可以通过装饰器将折扣方案的函数添加到promos,这种称为注册装饰器
from collections import namedtuple Customer = namedtuple('Customer', 'name fidelity') # 顾客结构体 class LineItem: # 单个商品 def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): # 计算商品的总价 return self.quantity * self.price class Order: # 上下文 订单类 def __init__(self, customer, cart, promotion=None): # 包含的信息:客户信息, 购物车, 折扣策略 self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, '__total'): self._total = sum([item.total() for item in self.cart]) return self._total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion(self) # 此时折扣直接使用promotion计算,self此时是order return self.total() - discount def __repr__(self): # 控制输出的格式 fmt = '<order total: {:.2f} due: {:.2f}>' return fmt.format(self.total(), self.due()) # def print_info(self): # 控制输出的格式 # fmt = '<order total: {:.2f} due: {:.2f}>' # print(fmt.format(self.total(), self.due())) # 定义一个注册装饰器 promos = [] def promotion(promo_func): promos.append(promo_func) # 注册新定义的折扣函数 return promo_func # 定义各个策略为函数 @promotion def fidelity_promo(order): return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 @promotion def bulk_item_promo(order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total()*0.1 return discount @promotion def large_order_promo(order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * 0.07 return 0 # 利用了装饰器后,定义完的函数自动添加到promos中,不用再手动添加 # 最佳折扣方式 def best_promo(order): return max(prom(order) for prom in promos) # 返回最大的折扣 if __name__ == "__main__": joe = Customer("Joe D", 900) # 无积分顾客 ann = Customer("Ann jane", 1200) # 有积分顾客 cart = [LineItem("banana", 23, 0.5), # 购物车信息 LineItem("apple", 6, 1.5), LineItem("melon", 8, 1.2)] order1 = Order(ann, cart, fidelity_promo) order2 = Order(joe, cart, best_promo) # 在这里使用best_promo()函数得到最大的折扣 print(order1) print(order2)
闭包
1.变量的作用域:
b = 6 def f1(a): print(a) print(b) b = 9 f1(2)
这段代码报错,虽然b定义在函数的前面,但是b并不是全局变量,因为在函数中为b进行了赋值,所以python在编译函数体的时候判断b为局部变量。
正确的写法如下:使用global声明
b = 6 def f1(a): global b print(a) print(b) b = 9 f1(2) print(b)
输出269
2.闭包:
闭包实际指延伸了作用域的函数,其中包含函数定义体中引用,但不在定义体中定义的非全局变量,关键是能访问定义体之外定义的非全局变量。
例如: 定义average()函数,作用是计算不断增加的系列值的均值
1. 通过类实现:
class Average: def __init__(self): self.series = [] def __call__(self, new_value): # 可调用模拟 self.series.append(new_value) return sum(self.series)/len(self.series) if __name__ == "__main__": avg = Average() print(avg(1)) print(avg(2)) print(avg(3))
高阶函数实现:
def make_average(): # 定义高阶函数 series = [] def average(new_value): series.append(new_value) return sum(series)/len(series) return average if __name__ == "__main__": avg = make_average() print(avg(1)) print(avg(2)) print(avg(3))
注: 在make_average()函数中,series是局部变量,在调用avg(n)时,make_average()函数已经返回了,而他的本地作用域不在了。在average()函数中,series称为自由变量,指未在本地作用域中绑定的变量,average的闭包延伸到它的作用域之外所以讲“ 闭包实际指延伸了作用域的函数,其中包含函数定义体中引用,但不在定义体中定义的非全局变量,关键是能访问定义体之外定义的非全局变量。 ”
3.python3中的nonlocal
上面的make_average()的另一种实现方式,只保留sum,和count,而不用每次更新列表的指:
一种错误的写法:
def make_average(): # 定义高阶函数 count = 0 total = 0 def average(new_value): count += 1 total += new_value return total / count return average if __name__ == "__main__": avg = make_average() print(avg(1)) print(avg(2)) print(avg(3))
这里报错:
UnboundLocalError: local variable 'count' referenced before assignment
这里的count,total不再是自由变量了,因为在average()中给他们赋值,所以他们变成了局部变量。当count,total是数字或不可变对象(string, tuple)时,都会出现这种错误,上述series没有报这种错误是因为列表是可变对象。
nonlocal的作用就是将变量标记为自由变量,即使在函数中赋新值,也是自由变量,且闭包中保存的绑定会更新。
def make_average(): # 定义高阶函数 count = 0 total = 0 def average(new_value): nonlocal count, total count += 1 total += new_value return total / count return average if __name__ == "__main__": avg = make_average() print(avg(1)) print(avg(2)) print(avg(3))
闭包,自由变量,可变对象。
--------------------------------------------------end-----------------------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)