python装饰器的理解与使用

2020-06-21   154 次阅读


请注意,本文编写于  398  天前,最后编辑于  234  天前,内容可能已经不具有时效性,请谨慎参考。

摘要:本篇主要描述什么是python的装饰器,以及装饰器是如何使用和工作原理。

装饰器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

我们来看个例子:

def demo1():
	print("I'm demo1.")

但现在我需要在调用函数时记录执行日志,于是在代码中添加:

def demo1():
	print("I'm demo1.")
	logging.info("demo1 is running.")

但是如果其他函数也需要的话一个一个添加就会很麻烦。

接下来我们可以写一个函数来代替一个个添加。

def use_logging(func):
	logging.info("%s is running."%func.__name__)
	func()

def demo1():
	print("I'm demo1.")
	
use_logging(demo1)

逻辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行demo1(),但是现在不得不改成use_logging(demo1)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

简单装饰器

先看这个新的例子来解释闭包:

# print_msg是外围函数
def print_msg():
    msg = "I'm closure"

    # printer是嵌套函数
    def printer():
        print(msg)

    return printer


# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()

msg是一个局部变量,在print_msg函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。

结合这个例子再看维基百科的解释,就清晰明了多了。闭包就是引用了自有变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在。

下面看看python中的装饰器:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        print("call %s():"%func.__name__)
        print("args = {}".format(*args))
        return func(*args,**kwargs)
    return wrapper()

调用它:

@log
def test(p):
    print(test.__name__ + "param:"+p)

test("I'm param")

输出:

image-20200621230457462

装饰器在调用是时候使用了@语法,其实实际上的调用如下:

def test(p):
	print(test.__name__ + "param:"+p)
wrapper = log(test)
wrapper ("I'm param")

@语法只是将函数传入装饰器函数,并无神奇之处。

值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。可以尝试去除@functools.wraps(func),你会发现test.__name__的输出变成了wrapper。

带参数的装饰器

装饰器允许传入参数,一个携带了参数的装饰器将有三层函数,如下所示:

import functools

def log_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('log_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper

    return decorator
    
@log_with_param("param")
def test_with_param():
    print(test_with_param.__name__)
test_with_param()

看到这个代码是不是又有些疑问,内层的decorator函数的参数func是怎么传进去的?和上面一般的装饰器不大一样啊。

其实道理是一样的,将其@语法去除,恢复函数调用的形式一看就明白了:

# 传入装饰器的参数,并接收返回的decorator函数
decorator = log_with_param("param")
# 传入test_with_param函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")

输出结果与正常使用装饰器相同:

image-20200621232456603

至此,装饰器这个有点费解的特性也没什么神秘了。

装饰器这一语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。

Python中引入了很多函数式编程的特性,需要好好学习与体会。

本文由 hongCYu 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
原文链接:https://hongcyu.cn/posts/python-decorator.html
最后更新于:2020-12-03 16:24:37

Coffee