Python基础-------装饰器用法 & functools模块简单介绍

本文深入探讨Python中的装饰器和高阶函数概念,讲解如何使用装饰器增强函数功能,实现业务逻辑与辅助功能的分离,同时保持代码的清晰与模块化。文中详细解释了装饰器的语法糖、柯里化、带参数装饰器的使用,以及如何解决装饰器带来的副作用,如函数属性的丢失。此外,还介绍了如何利用Python内置的functools模块来优雅地解决这些问题。

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

装饰器

需求
一个加法函数,想增强它的功能,能够输出被调用过的以及调用的参数信息

def add(x, y):
    return x + y 
#增加信息输出功能
def add(x, y):
    print("call add,x + y")#日志输出到控制台
    return x + y

上面的加法函数是完成了需求,但是有以下的缺点

  • 打印是一个功能,这条语句和add函数耦合太高
  • 加发函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数add中
    下面的代码做到了业务功能分离,但是fn函数调用传参是个问题
def add(x,y):
    return x + y

def logger(fn):
    print('begin')#增强的输出
    x = fn(4,5)
    print('end')
    return x

print(logger(add))

解决了传参的问题,进一步改变

def add(x,y):
    return x + y

def logger(fn,*args,**kwargs):
    print('begin')
    x = fn(*args,**kwargs)
    print('end')
    return x

print(logger(add,5,y=6))

柯里化

def add(x,y):
    return x + y
def logger(fn):
    def wrapper(*args,**kwargs)
        print('begin')
        x = fn(*args,88kwargs)
        print('end')
        return x 
    return wrapper

装饰器语法糖

def logger(fn):
    defwrapper(*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return wrapper

@logger    #等价于add = logger(add)
def add(x,y):
    return x + y
print(add(45,40))
  • 装饰器和高阶函数
    装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
import datetime
import time

def logger(fn):
    def wrapper(*args,**kwargs):
        print("args={},lwargs={}".format(args,kwargs))
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        duration = datetine.datetime.now() - start
        print("function {} took {}s.".format(fn.__name__,duration.total_seconds()))
        return ret
    return wrapper
@logger
def add(x,y):
    print("========call add=========")
    time.sleep(2)
    return x + y

print(add(4,y=7))

Python的文档

  • Python文档字符串Documentation Strings
  • 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
  • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
  • 可以使用特殊属性__doc__访问这个文档
def add(x,y):
    """Thin is a function of addition"""
    return x + y

print("name = {}\ndoc = {}".format(add.__name,add.__doc__))
print(help(add))
  • 装饰器的副作用
    被包装函数的name属性是包装函数的,我们需要更改包装函数的参数注解
def logger(fn):
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x 
    return wrapper

@logger      #add = logger(add)
def add(x,y):
    '''this is a function for add'''
    return x + y

print("name={},doc={}".format(add.__name__,add.__doc__))

提供一个函数,被封装函数属性 copy>包装函数属性

def copy_properties(src,dst):#可以改造成装饰器
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
def logger(fn):
        def wrapper(*args,**kwargs):
        'I am wrapper'
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    copy_properties(fn,wrapper)
    return srapper
@logger    #add = logger(add)
def add(x,y):
    ''This is a funtion for add''
    return x + y
print("name={},doc={}".format(add.__name__.add.__doc__))

通过copy_properties函数将被包装函数的属性覆盖掉包装函数
凡是被装饰的函数都需要复制这些属性,这个函数很通用
可以将复制属性的函数构建成装饰器函数,带参装饰器
提供一个函数,被封装函数属性 copy>包装成函数,该找成带参装饰器

def copy_properties(src):  # 柯里化
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy
def logger(fn):
    @copy_properties(fn)   #wrapper = copy_properties(fn)(wrapper)
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('begin')
        x =fn(*args,**kwargs)
        print('end')
        return x
    return wrapper

@logger #add = logger(add)
def add(x,y):
    '''This is a funtion for add'''
    return x + y
print("name={},doc={}".format(add.__name__,add.__doc__))

*需求:获取函数的执行时长,对时长超过阀值的函数记录一下

def logger(duration):
    def _logger(fn):
        @copy_properties(fn)   #wrapper = wrapper(fn)(wrapper)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now():
            ret = fn(*args,**kwargs)
            delta = (datetion.datetime.now() - start).total_seconds()
            print('so slow;) if defta > duration else print('so fast')
            return ret
        return wrapper
    return _logger

@logger(5)   #add = logger(5)(add)
def add(x,y):
    time.sleep(3)
    return x + y

print(add(5,6))

带参装饰器

  • 它是一个函数
  • 函数作为它的形参
  • 返回值是一个不带参的装饰器函数
  • 使用@functionname(参数列表)方式调用
  • 可以看做在装饰器外层有加了一层函数,这个函数可以多参数
    将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
def logger(duration,func=lambda name,delte:print('{} took {:2f}s'.format(name,delta))):
    def _logger(fn):
        @copy_properties(fn)    #wrapper = wrapper(fn)(wrapper)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__,delta)
            return ret
        return wrapper
    return _logger

functools模块

  • functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,update=WRAPPER_UODATES)
    类似copy_properties功能
    wrapper包装函数,被更新者,wrapped被包装函数,数据源
    元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
    ‘__module__’,’__name__’,’__qualnname__’,’__doc__’,’__annotations__’
    模块名,名称,限定名,文档,参数注解
    元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
    增加一个__wrapped__属性,保留着wrapped函数
import datetime,time,functools
def logger(duration,func=lambda name,duration:print('{} took {}s'.format(name,duration))):
    def _logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__,duration)
            return ret
        return  functools.update_wrapper(wrapper,fn)
    return _logger

@logger(1)   #add = logger(5)(add)
def add(x,y):
    time.sleep(2)
    return x + y

print(add(5,6),add.__wrapped__,add.__name__,add.__doc__,add.__dict__)
  • @functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
    类似copy_properties功能
    wrapped被包装函数
    元组WRAPPER_ASSIGNMENTS中要被覆盖的属 性
    ‘__module__’,’__name__’,’__qualnname__’,’__doc__’,’__annotations__’
    模块名,名称,限定名,文档,参数注解
    元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
    增加一个__wrapped__属性,保留着wrapped函数
import datetime,time,functools
def logger(duration,func=lambda name,duration:print('{} took {}s'.format(name,duration))):
    def _logger(fn):
        @functools.wraps(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__,duration)
            return ret
        return  wrapper
    return _logger

@logger(1)   #add = logger(5)(add)
def add(x,y):
    time.sleep(2)
    return x + y

print(add(5,6),add.__wrapped__,add.__name__,add.__doc__,add.__dict__)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值