【py3】装饰器

本文将从以下几点讨论python3中的装饰器:

  • 闭包传递函数引用

  • 什么是装饰器

  • @注解完成装饰器

  • 装饰器的装饰time

  • 装饰带参数的函数

  • 装饰带返回值的函数

  • 装饰器带参数

  • 多个装饰器

  • 保留被装饰函数的__name__属性

在python中函数,对象统统都可以赋值给变量,如果把一个函数赋值给一个变量,那么通过这个变量就可以调用函数。有关闭包可查阅小编涂鸦之作—【python3闭包】。这里我们将函数直接传到闭包里完成调用。

def log(func):
    def wrap():
        func()
    return wrap



def echo():
    print("hello decorator!")


f = log(echo)

f()

------------------------------------------

hello decorator!

什么是装饰器

通常有些函数功能是非业务性质的(权限、日志、事务等),我们希望将这些函数功能植入到业务函数处理中,又不想破坏业务函数的定义,这种在运行期动态增强功能的方式就是装饰器。上面的示例中,显然已经拿到了目标函数的引用,我们就可以在目标函数执行前、执行后进行增强。这里我们在函数执行前后打印日志。

def log(func):
    def wrap():
        print("log start...")
        func()
        print("log end...")
    return wrap



def echo():
    print("hello decorator!")


f = log(echo)

f()

------------------------------------------

log start...

hello decorator!

log end...

注解完成装饰器

结合示例1和示例2我们就完成了装饰器的使用,没有改变目标函数定义,增强了目标函数功能。但是在调用上略显吃力,我们可以直接使用@+装饰器函数置于目标函数上,完成装饰操作。

def log(func):
    def wrap():
        print("log start...")
        func()
        print("log end...")
    return wrap



@log

def echo():
    print("hello decorator!")



echo()
------------------------------------------

log start...

hello decorator!

log end...

装饰器的装饰time

python3是解释型编程语言,从上至下依次解释执行。所以在目标函数执行前装饰器已经装饰好了。下面我们并没有调用任何函数,执行时装饰器已生效。

def log(func):

    print("decorator...")
    def wrap():

        print("log start...")
        func()
        print("log end...")
    return wrap



def echo():
    print("hello decorator!")

------------------------------------------

decorator...

装饰带参数的函数

很多时候目标函数都是带参数的,这里使用标准的不定长传参,注意在调用目标函数时,元组参数args要加*,关键字参数kwargs加**,用于拆包,否则目标函数接收到的是一个元组和字典。函数定义时,*args和**kwargs用来声明将元组参数接收到args中,将关键字参数接收到kwargs中。

import time

def log(func):
    print("decorator...")
    def wrap(*args, **kwargs):
        start = time.time()
        print("func start at %f" % start)
        func(*args, **kwargs)
        end = time.time()
        print("func end at %f, elaps: %f" % (end, (end - start)))
    return wrap

@log
def echo(name, *args, **kwargs):
    time.sleep(1)
    print(name, args, kwargs)
if __name__ == '__main__':
  echo("码农小麦", 100, 200, age=30)

------------------------------------------

decorator...

func start at 1611731478.221400

码农小麦 (100, 200) {
    
    'age': 30}

func end at 1611731479.221400, elaps: 1.000000

装饰带返回值的函数

目标函数有返回值的情况很容易处理,经过上面的示例,我们知道装饰器就是将目标函数作为变量传入,然后由装饰器触发目标函数。带有返回值的函数,只需由装饰器内部接收返回值,并返回即可。

def log(func):
    def wrap(*args, **kwargs):
        return func(*args, **kwargs)
    return wrap

@log
def echo():
  return "200 ok"



if __name__ == '__main__':
    print(echo())

------------------------------------------

200 ok

装饰器带参数

很多时候装饰器自身也需要参数支持,比如记录日志我们希望知道是哪个业务模块的,或日志的级别等。上面的示例装饰器用来接收函数变量名了,装饰器内部的嵌套函数用来接收目标函数参数了,所以装饰器想要接收参数只能再来一层嵌套函数了。

def log(name):
    def wrap(func):
        def logging(*args, **kwargs):
            print("%s log start..." % name)
            func(*args, **kwargs)
            print("%s log end..." % name)
        return logging
    return wrap

@log("order")
def echo(num):
    print("echo %d" % num)



if __name__ == '__main__':
    echo(200)
------------------------------------------

order log start...

echo 200

order log end...

多个装饰器

有时需要将多个装饰器作用于目标函数,使用上和单个装饰器并无区别。下面我们使用两个装饰器作为演示,其实装饰器本质就是传递函数引用,且只能作用于函数。第一个装饰器找不到函数就会向下继续执行,第二个装饰器找到目标函数进行装饰然后返回内部函数,第一个装饰器就以第二个装饰器返回的函数作为目标函数,所以装饰器最后的执行顺序就是装饰器声明的顺序,而装饰的顺序是由近到远(远近是相对于目标函数而言)。

import time

def calc_time(func):
    print("calc_time decorator...")
    def wrap():
        start = time.time()
        print("time start at %f..." % start)
        func()
        end = time.time()
        print("time end at %f, elaps: %f" % (end, (end - start)))
    return wrap

def log(func):
    print("log_biz decorator...")
    def wrap():
        print("log start...")
        func()
        print("log end...")
    return wrap

@calc_time
@log
def echo():
    print("hello python3")

if __name__ == '__main__':
  echo()

------------------------------------------

log_biz decorator...

calc_time decorator...

time start at 1611736239.676400...

log start...

hello python3

log end...

time end at 1611736239.676400, elaps: 0.000000

保留被装饰函数的__name__属性

目标函数被装饰器装饰后,由于目标函数是作为参数传递到装饰器函数中,而装饰器函数内部是返回嵌套函数的引用,所以查看目标函数的__name__属性时,发现属性名称为装饰器内部嵌套函数名称。我们并不需要对__name__属性进行赋值操作,python内置functools.wraps可以解决这个问题。

import functools

def log(func):
    def wrap():
        func()
    return wrap

def log1(func):
    @functools.wraps(func)
    def wrap():
        func()
    return wrap

@log
def echo():
    print("hello decorator!")

@log1
def echo1():
    print("hello decorator!")

if __name__ == '__main__':
  print(echo.__name__)
  print(echo1.__name__)

------------------------------------------

wrap

echo1

猜你喜欢

转载自blog.csdn.net/weixin_43275277/article/details/113568207