Python学习笔记--带参数的装饰器

本文探讨了Python装饰器的副作用,解释了如何通过柯里化创建带参数的装饰器,使用`functools`模块进行功能增强,并提供了具体的示例。此外,还讨论了多参数装饰器的实现,最后进行了总结,强调装饰器是接收和返回函数的高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装饰器的副作用

def fn():
    '''this is fn'''

help(fn)
#结果:
Help on function fn in module __main__:

fn()
    this is fn

这就是一个函数的文档。
这里help函数好像其实就是调用了函数下面的注释。
其实和默认值一样,文档是存在__doc__里面的,如下:

print(fn.__doc__)
# 结果
this is fn

我们可以通过dir(fn)来获取它的所有属性。

import datetime

def logger(fn):
    def warp(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret
    return warp

import time

@logger
def sleep(x):
    time.sleep(x)
    
>>> sleep.__name__
'warp'

如上的装饰器,这里可以发现调用__name__属性后出来的是wrap而不是sleep,这就是用了装饰器后的副作用。
当然,我们可以通过赋值的方式来解决:

import datetime

def logger(fn):
    def warp(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret
    warp.__name__ = fn.__name__
    warp.__doc__ = fn.__doc__
    return warp

import time

@logger
def sleep(x):
    time.sleep(x)

print(sleep.__name__)
# 结果
sleep

或者采用调用函数的方式:

def copy_properties(src, dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__

def logger(fn):
    def warp(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret
    copy_properties(fn, warp)
    # warp.__name__ = fn.__name__
    # warp.__doc__ = fn.__doc__
    return warp

柯里化

柯里化其实是一个数学概念,目的是让多个参数变成一个参数。

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
    return _copy

def logger(fn):
    def warp(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret

    copy_properties(fn)(warp)
    # copy_properties(fn, warp)
    # warp.__name__ = fn.__name__
    # warp.__doc__ = fn.__doc__
    return warp

这个就叫柯里化。
这里copy_properties返回的是一个装饰器。于是可以改造成如下:

def logger(fn):
    @copy_properties(fn)
    def warp(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret
    return warp

这里柯里化的目的就是为了做这个带参数的装饰器。

functools

import functools
help(functools.wraps)

>>> help(functools.wraps)
Help on function wraps in module functools:

wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'), updated=('__dict__',))
    Decorator factory to apply update_wrapper() to a wrapper function

    Returns a decorator that invokes update_wrapper() with the decorated
    function as the wrapper argument and the arguments to wraps() as the
    remaining arguments. Default arguments are as for update_wrapper().
    This is a convenience function to simplify applying partial() to
    update_wrapper().

用法:

import functools
#help(functools.wraps)

def logger(fn):
    @functools.wraps(fn)
    def wrap(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = datetime.datetime.now()
        print('{} called took {}'.format(fn.__name__, end - start))
        return ret
    return wrap

@logger
def sleep(x):
    time.sleep(x)

sleep(3)

# 结果
sleep called took 0:00:03.015098

这里@functools.wraps(fn)就替代了@copy_properties(fn)的作用。

带参数的装饰器

另外,functools.wrap还带有参数:
假如现在只想记录大于1秒的时间,并且对这个1秒单位作控制,通过参数传递进来。

时间差计算:

>>> s = datetime.datetime.now()
>>> e = datetime.datetime.now()
>>> type(e-s)
<class 'datetime.timedelta'>

如下,计算这个属性:

>>> delta = e - s
>>> delta.total_seconds()
2.3e-05

所以整个logger装饰器:

def logger(s):
    def _logger(fn):
        @functools.wraps(fn)
        def wrap(*args, **kwargs):
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)
            end = datetime.datetime.now()
            if (end-start).total_seconds() > s:
                print('call {} took {}'.format(fn.__name__, end - start))
            return ret
        return wrap
    return _logger

这样我们就定义了一个带参数的装饰器:

@logger(2)
def sleep(x):
    time.sleep(x)

sleep(2)
# 结果
call sleep took 0:00:02.016245

这种就叫做带参数的装饰器。一个函数,返回一个不带参数的装饰器。

多参数

def logger(s, p=lambda name, t:print('call {} took {}'.format(name, t))):
    def _logger(fn):
        @functools.wraps(fn)
        def wrap(*args, **kwargs):
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)
            end = datetime.datetime.now()
            if (end-start).total_seconds() > s:
                p(fn.__name__, end - start)
            return ret
        return wrap
    return _logger

@logger(2)
def sleep(x):
    time.sleep(x)

sleep(3)
# 结果
call sleep took 0:00:03.003540

这里将打印直接变成了匿名函数放到了参数里面。这样打印要更加的灵活。
(后续没太明白,等回滚一遍了再来)

小结

装饰器是一个参数是函数,返回值也是函数的参数。
调用的时候是一个个作为参数传进去的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值