《什么是Python函数装饰器?看完这一篇(入门)就够了》

时间: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.装饰器通常在一个模块中定义,然后应用到其他模块中的函数上(即跨模块使用)

###欢迎大家交流指正,转载请备明原文!###

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值