流畅的python:函数装饰器-Part1

函数装饰器(上)

就像我前面所讲的那样,我不止一次在面试中被问到装饰器,这章我会为你揭开它的面纱。

1、装饰器基础知识

不要把装饰器想的那么复杂,装饰器就是一个可调用的对象,只不过其参数是一个函数对象(如果你看过上一章,应该知道函数就是一个对象,可以作为参数进行传递),和我们往函数里面传入列表对象,整数对象并没有什么差别,我们先看一个简单的例子:

# 示例1
def deco(af):
    def inner():
        print('来自inner函数的输出')

    print('deco-inner ID: %d' % id(inner))
    return inner


def target():  # 用deco来装饰target
    print('来自target函数的输出')


print('未装饰前target ID: %d' % id(target))
target = deco(target)

target()
print('装饰后target ID: %d' % id(target))

# 返回
未装饰前target ID: 2747427204840
deco-inner ID: 2747422507080
来自inner函数的输出
装饰后target ID: 2747422507080

我们首先在deco里面设置了一个嵌套函数inner,其id为080,接下来我们使用使用了target = deco(target)语句,传入原始id为840的target函数对象,但是返回的inner对象,并将inner对象赋值给target,所以target的id必然发生变换,因为target变量所指向的对象都变了。这就是装饰器,传入一个函数,然后返回(替换为)另一个函数。

严格来说,装饰器只是语法糖,可以直接将其视为一个可调用对象,装饰器可以写成下面两种形式:

# 示例2
@deco
def target(): # 用deco来装饰target
	print('来自target函数的输出')
def target():  
    print('来自target函数的输出')
target = deco(target) # 用deco来装饰target

两者是等价的,但是第一种更常用一些。

2、Python何时执行装饰器

尽管这两种形式都属于装饰器的定义,但是我们也可以看出装饰器的一个关键特性:装饰器在被装饰的函数定义之后立即运行。这个不难理解,因为参照示例2的第二端代码,在定义装饰器的时候有一个赋值语句,所以在定义之后立即运行。当然,我们要被装饰的target对象只有在调用的时候才会执行

3、实现一个简单的函数装饰器

我们以一个例子来看装饰器的实现过程。被装饰函数是一个计算阶乘的函数,现在想不更改原始函数的基础上记录每一次计算所耗费的时间,具体实现过程如下:

import time
# 装饰器最外层输入参数是一个函数
def clock(func): # 1
    name = func.__name__
    # 如果存在其他内部参数应嵌套一个内部函数进行参数获取
    def clocked(*arg):
        n = 1
        if arg:
            n = int(arg[0])
        start = time.perf_counter()
        # 内部调用被装饰函数获取结果
        result = func(n) # 2
        cost_time = time.perf_counter() - start
        print("[{0:.8f}s] {1:s}({2:d})->{3:d}".format(cost_time, name, n,
                                                      result))
        return result # 3
# 内部函数返回真实计算结果
# 最外层函数返回函数对象
    return clocked

# 开始装饰啦
@clock # 4
def factoral(n):
    return 1 if n < 2 else n * factoral(n - 1)

>>>factoral(6)
[0.00000030s] factoral(1)->1
[0.00005200s] factoral(2)->2
[0.00008090s] factoral(3)->6
[0.00019350s] factoral(4)->24
[0.00022020s] factoral(5)->120
[0.00023160s] factoral(6)->720
720
  • 1、 注意,如果原始函数需要传递参数,需要定义内部嵌套函数来接收参数,外部函数只接收被装饰的函数对象。

  • 2、 出现嵌套函数就注意闭包啦!像内部函数result = func(n)之所以可以被使用,就是因为func成为了一个自由变量哦,如果不知道,赶紧回去看看嵌套函数与闭包吧。

  • 3、内部函数返回真实的结果,外部函数一般都是返回内部函数取代被装饰函数

  • 4、还记得不,这里等价于factoral=clock(factoral),这两个factoral已经不是一个对象啦,赋值语句左侧获取的新factoral对象其实就是披着羊皮(factoral)的狼🐺(clocked)。你不信可以看看现在的factoral的真实名字:

    >>>factoral.__name__
    'clocked'
    

    你看,我就说吧。突然想起来一个成语:偷天换日

如果你对这种挂羊头卖狗肉的factoral感到耻辱,实时上也存在一定的补救措施functools.wraps,functools.wraps是标准库中拿来即用的装饰器之一,可以把相关的属性从func复制到clocked中,此外还能正确处理关键字参数。

import time
import functools
def clock(func):
    name = func.__name__
    @functools.wraps(func) # 注意这里哦,就加了这一个地方

    def clocked(*arg,**kwargs):
        n = 1
        if arg:
            n = int(arg[0])
        start = time.perf_counter()
        result = func(n)
        cost_time = time.perf_counter() - start
        print("[{0:.8f}s] {1:s}({2:d})->{3:d}".format(cost_time, name, n,
                                                      result))
        return result
    return clocked

现在我们再来看一下这个被装饰对象的name

>>>factoral.__name__
'factoral'

这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外操作。

——未完待续——
欢迎关注我的微信公众号
扫码关注公众号

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值