装饰器详解

851 阅读4分钟

第一次认识装饰器,其实是在学习 Python 的过程了解到的,后来在 JS 中也了解到有对应的实现。最近公司的 TypeScript 项目中自己也写了一个装饰器。

从不同语言对装饰器的实现,可以看出不同语言之间也在相互借鉴。下面我就用 Python 的代码来介绍装饰器。

什么是装饰器

装饰器本质上是一个函数。通过接收原始函数,装饰器可以在原始函数执行添加业务逻辑,使用装饰器的好处可以不污染原始函数。

使用装饰器

从上面的图中可以知道,使用装饰器的时候,会将原始函数加上一层装饰器中的代码,装饰器的代码并没有侵入到原始函数中,只是在调用原始函数前后,执行装饰器中的代码。

如果不使用装饰器,则需要在原始函数中加入装饰器中的逻辑,这种做法虽然可以实现与装饰器相同的功能,理解起来要更容易,但是这种写法并不能提高复用性。

不使用装饰器

了解了装饰器之后,我们来看看如何写一个装饰器。

编写装饰器

我们可以用一个打印函数来记录函数执行时间的日志装饰器。

import datetime


def log(func):
    def wrapper(*args, **kw):
        print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
        return func(*args, **kw)
    return wrapper

def hello():
    print('hello decorator')

hello = log(hello)

hello()

其中 log 函数就是一个装饰器,接受一个原始函数作为参数,最后返回一个 wrapper 函数,wrapper 函数内先执行打印日志,然后才调用原始函数。

hello 函数作为原始函数,使用 print 打印一串字符串。然后通过 log 函数返回的新函数重新赋值给 hello,这时的 hello 函数就具有了 log 函数中的打印日志的功能。

下面执行一下代码,看看结果是否能达到我们预期的那样。执行完会打印下面两行字符串,这也说明了 hello 函数包含了 log 的功能。

hello function was called in 2019-11-17 20:28:09.672132
hello decorator

装饰器的原理大概就是这样,只不过我们这种写法过于冗余,需要先声明 hello 函数,之后又将 log 函数的返回值重新赋值给 hello。Python 提供了语法糖,使用 @装饰器 代替上面的写法。当然还修复了函数的 __name__ 属性等,这里就不做展开了。

下面用语法糖体验一下装饰器。

import datetime


def log(func):
    def wrapper(*args, **kw):
        print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
        return func(*args, **kw)
    return wrapper

@log
def hello():
    print('hello decorator')

hello()

使用 @装饰器 之后代码更加简洁优雅。如果装饰器需要传递参数,则需要在装饰器添加多一层函数嵌套,通过最外层的函数获取参数。代码如下:

import datetime


def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print(text)
            print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('test')
def hello():
    print('hello decorator')

hello()

装饰器的用处

我当时学完装饰器之后的第一个想法是可以用来做鉴权。如果有些函数需要用户登陆后才能操作,那么我们可以写一个这样的装饰器,在装饰器内获取用户是否登录,如果登录则调用原始函数;未登录则提示用户进行登录。

如果不使用装饰器的做法,我们则需要在每个函数中编写是否登录的判断,这样不仅有大量重复的代码,而且代码变得不易维护。

当然装饰器的用法不仅于此,像 flask 框架则用装饰器来实现路由管理等等。

不同语言的装饰器只是在写法上略有区别,原理都是一样的。只要懂得原理,完全不用担心写法的差异。

关于装饰器的更多详细的内容就不做展开了,大家有兴趣可以到网络上查阅资料。

题目

最后留一道题目给大家分析一下

def dec_a(func):
    print('111')
    def wrapper(*args, **kw):
        print('222')
        func(*args, **kw)
        print('333')
    return wrapper

def dec_b(func):
    print('aaa')
    def wrapper(*args, **kw):
        print('bbb')
        func(*args, **kw)
        print('ccc')
    return wrapper

@dec_a
@dec_b
def test():
  print('test')

test()

如果文中有说的不对的地方,欢迎留言指出改正。如果你对装饰器有不同的理解,也欢迎留言讨论。


如果你喜欢我的文章,希望可以关注一下我的公众号【前端develop】

前端develop