时间:2021年5月29日
本文从基础概念出发,对函数装饰器进行解释。同时给出示例,讲解怎么使用函数装饰器。能够满足对函数解释器进行了解的基本需求。
我们学习一个知识点,一般都会按照这样的流程进行:为什么>>>是什么>>>怎么用。这里我也会按照这个思路对装饰器进行讲解。本文内容结构如下:
1.为什么需要函数装饰器?
使用函数装饰器可以让编程更加方便高效,特别是元编程(指在运行改变程序的行为)时。
是不是不太好理解?这不是相当于啥都没说吗。没关系,我们会在后面进行解释。先来看看什么是函数装饰器吧。
2.什么是函数装饰器?
装饰器:装饰器是可调用的对象,其参数是另一个函数(称为被装饰的函数)。
示例:
# 假设我们已经一个名为decorate的装饰器
@decorate
def target():
print('running target()')
在这个例子中,decorate就是一个装饰器,它的参数是函数target。而target称为被decorate装饰的函数,简称被装饰的函数。
上面的代码其实等价于下面的这种写法:
def target():
print('running target()')
target=decorate(target)
从target=decorate(target)中可以看到,decorate其实是一个函数,target作为这个函数的传入参数,decorate(target)最终返回在一个decorate中定义的函数,然后代替target。
还不是太懂?没关系,我们看看我们如何构造一个简单的装饰器。从对装饰器的构造中,进一步了解什么是装饰器。
3.构造一个简单的函数装饰器以及使用
# 装饰器通常会把函数替换成另一个函数,示例如下:
# 定义一个装饰器deco,返回inner函数对象
def deco(func):
def inner():
print('running inner()')
return inner
# 使用deco装饰target
@deco
def target():
print('running target()')
# 调用被装饰的target其实会运行inner
print(target())
# 审查对象,发现target现在是inner的引用
print(target)
输出:
running inner()
None
<function deco.<locals>.inner at 0x000002DA1ACE4598>
代码详解:
- 在print(target())中,因为target被deco装饰,即deco的参数是target,此时根据deco的定义,首先返回了一个inner,因此print(target())变成了print(inner()),而inner()由其定义可知,打印出’running inner()’,同时因为inner()函数没有return语句, 缺省的返回值就是None,由此可得到print(target())的最终输出结果为running inner()以及None。
- 在print(target)中,和上面的代码分析类似的,print(target) ->print(inner),注意这里并没有在inner后面加上(),即没有调用inner函数。因此打印的是inner的引用。
看完这个构造装饰器的例子后,是不是已经对装饰器的定义非常清楚了!
下面我们开始进一步研究装饰器。先给出装饰器的重要的2大特性:
- 1.能把被装饰的函数替换成其他函数
- 2.装饰器在加载模块时立即执行
4.对装饰器的2大特性进行解释
首先装饰器能把被装饰的函数替换成其他函数的这个特性,我们在上一节的例子中,其中已经给出了回答。上个例子中我们定义了一个deco装饰器,当给deco一个target函数作为参数时,返回的函数却是inner函数。
那么,装饰器的第二个特性:装饰器在加载模块时立即执行这个该如何理解?我们先看一个例子:
# 定义一个registration.py模块
registry = []
def register(func):
print("running register(%s)" % func)
registry.append(func)
return func
@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()
输出:
running register(<function f1 at 0x000002939DFC4510>)
running register(<function f2 at 0x000002939DFC4598>)
running main()
registry -> [<function f1 at 0x000002939DFC4510>, <function f2 at 0x000002939DFC4598>]
running f1()
running f2()
running f3()
由上面的例子可以知道:
-
运行模块后,在还未开始执行main()函数时,函数装饰器register立即执行,被装饰的函数函数只在明确调用时运行。因此,若只加载该模块而不运行该模块(即我们在python编辑器中输入import registration命令),也会输出:
running register(<function f1 at 0x000002939DFC4510>)
running register(<function f2 at 0x000002939DFC4598>) -
开始执行main()后,则开始执行main()函数中的语句。值得注意的是,在上面的例子中,函数装饰器register并没有修改返回的函数,因此f1()运行后,依旧是f1(),此时就直接输出f1()函数定义的输出。
好了,到这里我们应该已经能明白装饰器的2个重要的特性了。我们回过头来解释一下为什么需要使用函数装饰器。
5.再次解释为什么需要函数装饰器
同样的,我们先来一个例子:
#使用装饰器改进“策略”模式
from collections import namedtuple
# 网店有如下规则:
# 1.有1000或以上积分的顾客,每个订单打5%折扣
# 2.同一订单中,单个商品的数量达到20个或以上,打10%折扣
# 3.订单中的不同商品达到10个或以上,打7%折扣
# 简单起见:一个订单只能使用一个折扣
# 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 * self.quantity
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)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
promos = []
# 定义一个函数装饰器
def promotion(promo_func):
promos.append(promo_func)
return promo_func
@ promotion
def fidelity_promo(order): # 第一个具体策略
"""为积分为1000或以上的顾客提供5%折扣"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
@ promotion
def bulkItem_promo(order): # 第二个具体策略
"""单个商品为20个或以上时提供10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
@ promotion
def large_order_promo(order): # 第三个具体策略
"""订单中的不同商品达到10个或以上时提供7%折扣"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
def best_promo(order):
"""选择可用的最佳折扣"""
return max(promo(order) for promo in promos)
对这个例子进行简单的讲解:一个网店根据用户设置了不同的优惠政策,我们需要从这不同的优惠中找到最佳的优惠政策。同时,我们也需要随着网店提供的优惠政策进行动态的调整,即禁用已经失效的优惠政策、启动新增的优惠政策。
在没有学习装饰器之前,我们想想我们会怎么做?
是不是需要将定义的全部优惠政策的名称加入到promos这个列表中,并且每次新增或者禁用一个优惠政策时,需要对promos这个列表进行对应的新增和删除操作。这样的步骤繁琐而且容易忘记去修改promos列表。
现在学习了函数装饰器,你再也不用这样做了。在上例中,我们对某一个具体的优惠策略进行promotion装饰,就表示启用该优惠策略。而注释掉@ promotion后,就可以禁用该优惠策略。不再需要对promos列表做任何操作。只需要我们在定义优惠策略的时候加上函数装饰器即可。当然,上面这个例子还可以再进行简化,这里不再细究。
怎么样,到这是不是已经明白许多了,我们为什么要用函数装饰器了。
- 我们希望在程序运行的时候,用其他的函数替换掉某些函数(这个是装饰器的第一个特性)
- 在模块还未运行的时候就可以做一些事情(这个是装饰器的第二个特性)
- 更加灵活地和其他的功能进行搭配使用(我们举了一个网店不同的优惠的例子)
最后再给出装饰器在实际使用的时候的2个用法:
- 1.大多数装饰器会在内部定义一个函数,然后将其返回(即返回一个和被装饰的函数不同的函数)
- 2.装饰器通常在一个模块中定义,然后应用到其他模块中的函数上(即跨模块使用)
###欢迎大家交流指正,转载请备明原文!###