python基础-装饰器(一)-函数装饰器

装饰器本质上是一个 python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,或者说是抽离出与函数功能本身无关的雷同代码到装饰器中并继续重用,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。

一、简单装饰器

下面是一个简单的装饰器,实现打印出函数执行时的时间的功能。func_demo函数被当作参数传入starttime装饰器函数中。

import time

def starttime(func):
    def wrapper():
        print("当前时间:%s" %(time.strftime("%Y-%m-%d %H:%M:%S")))
        #func()           #直接执行函数也可以
        f = func()
        return f
        #return f+"!!!!!"    #可以修改函数的返回值
    return wrapper


@starttime
def func_demo():
    print("Hello world")

func_demo()

输出:

当前时间:2020-09-15 23:00:23
Hello world

二、可以接收函数参数的装饰器

函数带参数时,装饰器要做一些修改,函数带什么参数,装饰器就要接收什么参数(参数名一致)。但是每个函数的参数是不一样的,所以用可变参数传递,函数调用时传入的参数,位置参数用*args传递,关键字参数用**kwargs传递。

import time

def logging(func):
    def wrapper(*args, **kwargs):
        print("%s 运行函数的名称:%s" %((time.strftime("%Y-%m-%d %H:%M:%S")), func.__name__))
        f = func(*args, **kwargs)
        print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
        print("%s 函数运行结果:%s" % ((time.strftime("%Y-%m-%d %H:%M:%S")), f))
        return f
    return wrapper


@logging
def func_demo(x,y="bbbbb"):
    return str(x)+str(y)

result = func_demo("aaaaa", y="ccccc")
print(result)

输出:

2020-09-15 23:30:14 运行函数的名称:func_demo
函数参数:('aaaaa',) {'y': 'ccccc'}
2020-09-15 23:30:14 函数运行结果:aaaaaccccc
aaaaaccccc

三、装饰器带参数

装饰器也可以带参数,比如上面的例子中想让log指定级别输出。

eg1:

def use_logging(level="warn"):
    def logging(func):
        def wrapper(*args, **kwargs):
            if level=="warn":
                print("warn级别日志输出:")
                print("运行函数的名称:%s" %func.__name__)
            f = func(*args, **kwargs)
            if level=="debug":
                print("debug级别日志输出:")
                print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
            print("函数运行结果:%s" %f)
            return f
        return wrapper
    return logging

#装饰器不带参数时
@use_logging()
def func_1(x,y="bbbbb"):
    return str(x)+str(y)

#装饰器带参数时
@use_logging(level="debug")
def func_2(x,y="bbbbb"):
    return str(x)+str(y)

print(func_1("aaaaa", y="ccccc"))
print(func_2("aaaaa", y="ccccc"))

输出:

warn级别日志输出:
运行函数的名称:func_1
函数运行结果:aaaaaccccc
aaaaaccccc
debug级别日志输出:
函数参数:('aaaaa',) {'y': 'ccccc'}
函数运行结果:aaaaaccccc
aaaaaccccc

注:这种写法如果调用装饰器时不带参数,装饰器要加括号

eg2:

from functools import partial

def logging(func=None, level="warn"):
    if func==None:
        return partial(logging, level=level)    #partial()函数返回自身
    def wrapper(*args, **kwargs):
        if level=="warn":
            print("warn级别日志输出:")
            print("运行函数的名称:%s" %func.__name__)
        f = func(*args, **kwargs)
        if level=="debug":
            print("debug级别日志输出:")
            print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
        print("函数运行结果:%s" %f)
        return f
    return wrapper

#装饰器不带参数时
@logging
def func_1(x,y="bbbbb"):
    return str(x)+str(y)

#装饰器带参数时
@logging(level="debug")
def func_2(x,y="bbbbb"):
    return str(x)+str(y)

print(func_1("aaaaa", y="ccccc"))
print(func_2("aaaaa", y="ccccc"))

输出:

warn级别日志输出:
运行函数的名称:func_1
函数运行结果:aaaaaccccc
aaaaaccccc
debug级别日志输出:
函数参数:('aaaaa',) {'y': 'ccccc'}
函数运行结果:aaaaaccccc
aaaaaccccc

注:在第二个例子中,装饰器不带参数时,可以不加括号。当装饰器不带参数时,func_1会被当做第一个参数直接传递给logging装饰器,调用方式为logging(func_1),这就要求所有其他参数都必须有默认值。当装饰器带参数时,调用方式为logging(level="debug")(func_2),初始调用时,func_2并没有传递进来,这就要求装饰器内第一个参数必须是可选的(这也导致调用func_2时必须以关键字参数形式传参),而且要返回一个接受一个函数参数并包装它的函数,以functools.partial举例,参考partial()函数了解更多。

四、@wraps(func)

当装饰器作用在某个函数上时,这个函数的元信息比如名字、文档字符串、注解和参数签名都会丢失。

def decorator(func):
    '''
    装饰器
    '''
    def wrapper(*args:str, **kwargs):
        """底层包装函数"""
        result = func(*args, **kwargs)
        print("运行的函数:%s" %func.__name__)
        return result
    return wrapper

@decorator
def countdown(n:int):
    """
    当n大于0时,n=n-1
    """
    while n > 0:
        n -= 1

print(countdown.__name__)           #wrapper
print(countdown.__doc__)            #底层包装函数
print(countdown.__annotations__)    #{'args': <class 'str'>}

使用 functools 库中的 @wraps 装饰器注解底层包装函数可以解决这个问题。

from functools import wraps
def decorator(func):
    '''
    装饰器
    '''
    @wraps(func)   #注解底层包装函数
    def wrapper(*args:str, **kwargs):
        """底层包装函数"""
        result = func(*args, **kwargs)
        print("运行的函数:%s" %func.__name__)
        return result
    return wrapper

这时就可以显示原函数的信息了。

print(countdown.__name__)           
print(countdown.__doc__)            
print(countdown.__annotations__)    
countdown

    当n大于0时,n=n-1
    
{'n': <class 'int'>}

1. 函数装饰器可以直接在类中的方法上使用

logging装饰器为例

class Spam:
    @logging
    def output(self, x):
        return x

s = Spam()
print(s.output(5))

输出:

warn级别日志输出:
运行函数的名称:output
函数运行结果:5
5

2. 装饰器执行顺序:从内到外

@a
@b
@c
def f():
#等效于
f = a(b(c(f)))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值