《流畅的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-----------------------------------------------------

posted @   Alpha205  阅读(73)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示