python3装饰器函数

avatar 2020年5月16日20:43:01 评论 965 次浏览

什么是装饰器,装饰器有什么作用。我们为什么要学习装饰器,在什么时候需要用到装饰器。一般在说装饰器的时候,都会提到在用户登陆的时候会用到装饰器,装饰器的作用就是在原有的功能上添加一个其他的功能,比如插入日志,性能测试,事务处理,缓存,权限校验等等。之所以学习装饰器,是为了减少代码的重复性,并在指定的函数上加一个其他的函数。

#!/usr/bin/python3
#coding:utf-8
#~~~~~~~~~~~~www.wulaoer.org 吴老二个人博客~~~~~~~
def one(func):
    print("这是第一个函数")
    return func
@one
def two():
    print("这是第二个函数")

two()

输出结果:

这是第一个函数
这是第二个函数

这里记住,然后one函数中没有返回值,就会提示“TypeError: 'NoneType' object is not callable”这是因为函数没有return造成的,这就是装饰器的本质,返回函数的函数。因为给two函数加了一个装饰器,这里在调用two函数的时候先调用了one函数,后在调用two函数,这里的顺序要清楚。这里我们可以理解为在调用two函数之前给two函数增加一个函数。

#!/usr/bin/python3
#coding:utf-8
#~~~~~~~~~~~~www.wulaoer.org 吴老二个人博客~~~~~~~
from time import time, sleep
def run_time(func):
    def wrapper():
        start = time()
        func()                  # 函数在这里运行
        end = time()
        cost_time = end - start
        print("func time run time {}".format(cost_time))
    return wrapper

@run_time
def one():
    start = time()
    sleep(1)
    end = time()
    cost_time = end - start
    print("func one run time {}".format(cost_time))
@run_time
def two():
    start = time()
    sleep(1)
    end = time()
    cost_time = end - start
    print("func two run time {}".format(cost_time))

@run_time
def three():
    start = time()
    sleep(1)
    end = time()
    cost_time = end - start
    print("func three run time {}".format(cost_time))

one()
sleep(1)
two()
sleep(1)
three()
sleep(1)

输出结果:

func one run time 1.0035791397094727
func time run time 1.0036320686340332
func two run time 1.00187087059021
func time run time 1.0019142627716064
func three run time 1.0040409564971924
func time run time 1.004103183746338

这里写了三个统计函数运行时间的函数,有使用了一个嵌套函数做装饰器,把内部函数的返回值作为装饰器的返回值,这个用专业的来说是闭包,简单说就是函数内嵌套。然后每个函数上加一个@run_time,相当于家了一个装饰器,这叫通过闭包实现装饰器。

#!/usr/bin/python3
#coding:utf-8
#~~~~~~~~~~~~www.wulaoer.org 吴老二个人博客~~~~~~~
from time import time, sleep
def logger(msg=None):
    def run_time(func):
        def wrapper(*args, **kwargs):
            start = time()
            func()                  # 函数在这里运行
            end = time()
            cost_time = end - start
            print("[{}] func three run time {}".format(msg, cost_time))
        return wrapper
    return run_time

@logger(msg="One")
def fun_one():
    sleep(1)

@logger(msg="Two")
def fun_two():
    sleep(1)

@logger(msg="Three")
def fun_three():
    sleep(1)

fun_one()
fun_two()
fun_three()

输出结果:

[One] func three run time 1.0034739971160889
[Two] func three run time 1.0036709308624268
[Three] func three run time 1.0021088123321533

这是装饰器传参,带参数的装饰器通过前面简单的例子应该已经明白装饰器的价值和它的简单用法:通过闭包来实现装饰器,函数作为外层函数的传入参数,然后在内层函数中运行、附加功能,随后把内层函数作为结果返回。除了上述简单的用法还有一些更高级的用法,比如用装饰器进行类型检查、添加带参数的的装饰器等。它们的用法大同小异,关于高级用法,这里以带参数的装饰器为例进行介绍。不要把问题想的太复杂,带参数的装饰器其实就是在上述基本的装饰器的基础上在外面套一层接收参数的函数,下面通过一个例子说明一下。

#!/usr/bin/python3
#coding:utf-8
#~~~~~~~~~~~~www.wulaoer.org 吴老二个人博客~~~~~~~
import logging
from functools import partial
def wrapper_property(obj, func=None):
    if func is None:
        return partial(wrapper_property, obj)
    setattr(obj, func.__name__, func)
    return func

def logger_info(level, name=None, message=None):
    def decorate(func):

        logmsg = message if message else func.__name__

        def wrapper(*args, **kwargs):
            logging.log(level, logmsg)
            return func(*args, **kwargs)

        @wrapper_property(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel

        @wrapper_property(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg

        return wrapper

    return decorate
@logger_info(logging.WARNING)
def main(x, y):
    return x + y
main(3,3)
main.set_level(logging.ERROR)
main(5, 5)

输出结果:

WARNING:root:main
ERROR:root:main

我们还可以对装饰器添加一些属性,就如同给一个类定义实现不同功能的方法那样。以输出日志为例,初学Python的同学都习惯用print打印输出信息,其实这不是一个好习惯,当开发商业工程时,你很用意把一些信息暴露给用户。在开发过程中,我更加鼓励使用日志进行输出,通过定义WARNING、DEBUG、INFO等不同等级来控制信息的输出,比如INFO是可以给用户看到的,让用户直到当前程序跑到哪一个阶段了。DEBUG是用于开发人员调试和定位问题时使用。WARING是用于告警和提示。那么问题来了,如果我们预先定义一个打印日志的装饰器

10输出日志等级改成了ERROR。保留元信息的装饰器很多教程中都会介绍装饰器,但是大多数都是千篇一律的围绕基本用法在展开,少部分会讲一下带参数的装饰器,但是有一个细节很少有教程提及,那就是保留元信息的装饰器。什么是函数的元信息?就是函数携带的一些基本信息,例如函数名、函数文档等,我们可以通过func.name获取函数名、可以通过func.doc获取函数的文档信息,用户也可以通过注解等方式为函数添加元信息

avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: