Python装饰器详解

装饰器作为Python当中非常经典和实用的feature,在项目当中应用是非常广泛的,比如说记录运行时间,缓存,鉴权管理,等等都会使用到装饰器。我在学习装饰器的过程中,其实并不算非常的顺利,其中也遇到了不少的坑,不过当学完装饰器之后,使用起来,那别提有多爽了,在本文当中我将讲述一下我学习装饰器的历程,希望对大家有所启发,如有理解的不对或者不到位的地方,也欢迎各位读者斧正。

基础知识

函数作为Python当中的一等公民(first-class citizen),函数也是对象,可以吧函数复制与变量,代码如下:

def func(msg):
    print(f"Got a message {msg}")

seed_message = func
seed_message("Hello World!")

2020-10-19-mnzQaY

这里,我们吧函数func赋值给了变量send_message,这样调用send_message就相当于调用了func

函数同样可以作为参数传到另一个函数当中。

def get_message(msg):
    return f'Got a messsage: {msg}'

def deal_msg(deal_strategy, msg):
    print(deal_strategy(msg))

deal_msg(get_message, 'Hello World!')

2020-10-19-48ke3I

在这个例子当中,我们将函数get_message作为参数,传递到了deal_message当中,然后调用它。

可以在函数中定义函数,也就是函数的嵌套,代码如下:

def func(msg):
    def get_message(msg):
        print(f"Got a message: {msg}")
    return get_message(msg)

func("Hello World!")

2020-10-19-CxBMfb

这里,在函数func内部定义了一个函数get_message,然后调用后作为func的返回值返回。

函数的返回值也可以是函数对象(闭包),比如下面的例子:

def closure():
    def get_message(msg):
        print(f"Got a message: {msg}")
    return get_message

send_message = closure()
send_message("Hello World!")

2020-10-19-GaOunb

这里,函数closure的返回值是一个函数对象,之后将这个函数的返回值进行调用,得到了正确的输出。

简单的装饰器

有了上面的基础知识,我们可以来看今天的主角装饰器了,首先来看一个简单例子。

def decorator(func):
    def wrapper():
        print("wrapper of decorator")
        func()
    return wrapper

def retouched_func():
    print("This is retouched function.")

retouched_func = decorator(retouched_func)

retouched_func()

2020-10-19-PzeXwn

这里,我们通过函数作为参数传递给decorator函数,然后首先调用wrapper打印第一句,然后调用func也就是实际传递进来的参数retouched_func打印第二句,这里,我们改变了retouched_func的行为,但是retouched_func函数本身并没有发生变化,也就是说,这个方法是无入侵的。

实际上,这段代码虽然实现了装饰器的功能,但是,这个看起来太不pythonic了,在Python当中有更加简单和优雅的表示:

def decorator(func):
    def wrapper():
        print("wrapper of decorator")
        func()
    return wrapper

@decorator
def retouched_func():
    print("This is retouched function.")

retouched_func()

2020-10-19-nc8sgt

这里的@实际上是一个语法糖,相当于之前的retouched_func = decorator(retouched_func)写法,因为是语法糖,因此这个写法更加的简洁,因此,如果需要把一个函数做类似的装饰,只需要使用@就可以了。

带参数的装饰器

这里,就会有一个问题,如果说有参数需要传递给装饰需要怎么办呢? 一个非常简单的做法就是可以在对应的装饰器函数wrapper上添加对应的参数,例子如下:

def decorator(func):
    def wrapper(msg):
        print("wrapper of decorator")
        func(msg)
    return wrapper

@decorator
def retouched_func(msg):
    print(f"This is retouched function. params is {msg}")

retouched_func("Hello World!")

2020-10-19-tH2OZL

那么,问题又来了,如果说,有另外一个函数也想用这个装饰器,但是这个新的函数有两个餐宿,又该怎么办呢?

通常情况下会使用*args**kwargs,作为装饰器内部函数wrapper的参数,其中*args**kwargs,表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:

def decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

带有自定义参数的装饰器

装饰器不仅可以接受原函数任意类型和数量的参数,除此之外,它还可以接受自定义的参数,举个例子,我们想写一个装饰器重复执行同一个函数,然后在装饰器可以自定义执行的次数。

def repeat(num):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print(f"wrapper of decorator, func run {i} times")
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(4)
def retouched_func():
    print("Hello World!")

retouched_func()

2020-10-19-o0KM3t

修饰之后函数的变化

下面,我们来看一个有趣的现象,来看代码:

def decorator(func):
    def wrapper(msg):
        print("wrapper of decorator")
        func(msg)
    return wrapper

@decorator
def retouched_func(msg):
    print(f"This is retouched function. params is {msg}")

print(retouched_func.__name__)
help(retouched_func)

2020-10-19-b2rfJ6

这里,可以发现,retouched_func被修饰之后,他的元信息变了,这里的函数已经被wrapper取代了,为了解决这个问题,我们通常使用内置的一个装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("wrapper of decorator")
        func(*args, **kwargs)
    return wrapper

@decorator
def retouched_func(msg):
    print(f"This is retouched function. params is {msg}")

print(retouched_func.__name__)

2020-10-19-r3Uq25

类装饰器

前文主要介绍了函数作为装饰器的做法,实际上,类也可以作为装饰器,类装饰器主要依赖于函数__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次,直接来看代码吧:

class Decorator:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print('This is in class decorator!')
        return self.func(*args, **kwargs)

@Decorator
def example():
    print("Hello World!")

example()

2020-10-19-g4Qo3D

装饰器的嵌套

在Python当中,是支持多个装饰器的,调用顺序从上到下,来看一个例子就明白了:

import functools

def decorator1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)
    return wrapper


def decorator2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)
    return wrapper


@decorator1
@decorator2
def example(message):
    print(message)


example('Hello World!')

2020-10-19-NFRI5O

装饰器的用法示例

到这里,装饰器的基本概念和语法,我就描述完了,下面来看几个实际使用装饰器的例子。

身份认证

身份认证很容易理解,比如登录APP的时候需要输入用户名和密码,然后服务端就会确认你的身份,如果认证通过就可以进行某些操作,否则便不可以,下面我们来看一个简单的代码示例。


import functools

def authenticate(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        request = args[0]
        if check_user_logged_in(request):
            return func(*args, **kwargs)
        else:
            raise Exception('Authentication failed')
    return wrapper
    
@authenticate
def post_comment(request, *args, **kwargs)
    pass

这里,装饰器会首先检查是否登录, 如果已经登录,便可以执行post_commet的操作,否则便无法执行这个操作。

日志记录

日志记录是一个非常常见的案例,比如在实际工作中,我需要测试某个函数的执行时间,但是我又不想入侵这个函数添加计时的操作,那么装饰器就会极大的发挥它的无入侵性的这个特点:


import time
import functools

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
        return res
    return wrapper
    
@log_execution_time
def calculate_similarity(items):
    pass

这里,装饰器log_execution_time记录某个函数的运行时间,并返回其执行结果。如果你想计算任何函数的执行时间,在这个函数上方加上@log_execution_time即可。

缓存

缓存装饰器也非常的常见,Python就内置了一个LRU cache的装饰器,具体的细节请自行查阅相关资料,这里我就不过多解释了。

总结

本文介绍了Python当中装饰器的用法,

Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

然后介绍了几个装饰器典型的应用,合理使用装饰器,往往可以极大的提高程序的可读性和运行效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值