python函数设计与使用_Python函数拾遗 一等函数与设计模式 --流畅的python

本章就Python中函数与设计模式做一些探讨, notebook文件在这里.

一等函数与设计模式

虽然设计模式与语言无关,但是某些设计模式并不适用于某些语言. 下面就Python中策略模式做一些讨论.

策略模式

在设计模式中,策略模式的概述如下:定义一系列算法,把它们一一封装起来,并且使它们可以互相替换.本模式使得算法可以独立于使用它的客户而变化.

这里举的例子是电商的促销策略,即根据客户的属性或订单中的商品计算折扣(顺便吐槽近几年双十一的折扣是越来越复杂了...).

例如某个网店制定了如下的折扣策略:1000积分以上客户,享受5%折扣

同一订单下,单个商品数量达到20个或以上,享受10%折扣

订单的不同产品达到10个或以上,享受7%折扣

那么按照策略模式,我们需要有如下的类:

上下文把一些计算委托给实现的不同算法的可互换组件,它提供服务. 在该例子中,上下文即为订单Order,它会根据不同算法计算促销折扣.

策略实现不同算法的组建的共同接口. 在该例子中,即为Promotion.

具体策略策略的具体子类. 在该例子中,即为上述的三种折扣策略.

具体策略由上下文类的客户选择, 在此例子中,我们可以在实例化订单类之前(__init__)就以某种方式选择好策略, 然后将其作为Order类初始化的参数.

经典策略模式

from abc import ABC, abstractmethod

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.price * quantity

class Order:

def __init__(self, customer, cart, promotion=None):

self.customer = customer

self.cart = cart

self.promotion = promotion

class Promotion(ABC):

@abstractmethod

def discount(self, order):

"""返回折扣金额"""

def FidelityPromo(Promotion): # 以第一个具体策略为例

def discount(self, order):

return order.total() * 0.5 if order.customer.fiderlity >= 1000 else 0

在上面的代码中,给出了顾客的具名元组, 商品类和订单类的例子. 并且这里我们Promotion类是一个抽象基类(ABC), 并且用@abstractmethod装饰器来装饰抽象方法,没有具体实现该抽象方法的类无法被实例化.

虽然上面的实现没有问题,但是利用函数作为对象,我们可以更加精简地完成这一需求.

利用函数实现策略模式

回顾上面的实现,可以发现貌似我们的具体策略只有一个方法,似乎没有必要将其写成一个类.因此我们可以将具体策略换成简单的函数,并去掉抽象基类.

class Order:

def __init__(self, customer, cart, promotion=None):

self.customer = customer

self.cart = cart

self.promotion = promotion

def total(self):

if not hasattr(self, '__total'):

self.__taotal = 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)

return self.total() - discount

def fidelity_promo(order):

"""策略一 作为函数"""

return order.total() * 0.5 if order.customer.fiderlity >= 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

这样我们的Order类使用起来也根据简单了, 构造Order实例时直接传入函数作为参数.

选择最佳策略

下面假设需要一个"元策略", 来在所有具体策略中选择最优策略, 此时将函数作为对象很容易实现这一元策略(相比写一个类):

#策略列表

promos = [fidelity_promo, bulk_item_promo]

def best_promo(order):

return max(promo(order) for promo in promos)

这么写有一个弊端, 在于你需要不断维护策略列表,否则新加入的策略不会被考虑.

因此我们可以利用globals函数来找出模块中的全部策略.

该函数会返回当前的全局符号表,然后我们在符号表中找到以_promo结尾的函数.

promos = [globals()[name] for name in globals()

if name.endswith('_promo')

and name != 'best_promo']

def best_promo(order):

return max(promo(order) for promo in promos)

另一种写法是将所有具体策略函数写在一个单独的模块中(不包含元策略).然后利用inspect函数去找出该模块中所有函数.

# 下面的promotions是一个模块

promos = [func for name, func in

inspect.getmembers(promotions, inspect.isfunction)]

命令模式

将函数作为参数传递同样可以简化命令模式. 命令模式解耦了调用操作的对象(调用者)和提供实现的对象(接受者). 该模式需要在二者之间放一个Command对象,它只有一个方法即执行的接口.这有些类似上面的策略模式

有时Command类较为复杂,需要保存自身的一些信息,此时我们可以通过给该类实现__call__方法令其实例变成可调用对象.

你可能感兴趣的:(python函数设计与使用)