欢迎访问宙启技术站
智能推送

Python中的Decorators函数装饰器用法详解

发布时间:2023-06-15 13:19:08

在Python中,装饰器(decorators)是一种特别的函数,它可以对其他函数进行“包装”,并添加一些额外的功能,而不需要修改被装饰的函数本身。

举例说明,现在有一个“打招呼”的函数greet:

def greet(name):
    return "Hello, " + name

我们希望在执行greet函数前先打印一行log,可以使用函数装饰器来实现。

定义一个装饰器函数log:

def log(func):
    def wrapper(*args, **kwargs):
        print("Called function: ", func.__name__)
        return func(*args, **kwargs)
    return wrapper

装饰器函数log接收一个函数作为参数,返回一个新的函数wrapper。在wrapper函数中,首先输出log信息,然后调用原始函数(func)并返回其返回值。

现在,我们可以在greet函数前添加@log装饰器:

@log
def greet(name):
    return "Hello, " + name

greet("Bob")

运行greet函数会先输出log信息,然后输出"Hello, Bob"。

装饰器的语法糖@可以让Python代码更加简洁美观。

除了上面实现的log装饰器,还有许多常用的函数装饰器,如:

#### 缓存装饰器

def cache(func):
    memory = {}
    def wrapper(n):
        if n not in memory:
            memory[n] = func(n)
        return memory[n]
    return wrapper

cache装饰器会将函数的返回结果缓存起来,当再次调用时,首先查找缓存中是否存在,如果有,则直接返回缓存中的结果,否则调用原始函数,并将结果缓存起来。适用于计算量大,但结果相对不变的函数。

#### 计时装饰器

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print("Function {0} time elapsed: {1}s".format(func.__name__, end_time - start_time))
        return result
    return wrapper

timer装饰器会将函数的执行时间输出到控制台。适用于需要统计函数执行时间的情况。

#### 权限控制装饰器

def admin_required(func):
    def wrapper(user):
        if user != "admin":
            raise Exception("Permission denied")
        return func(user)
    return wrapper

admin_required装饰器会限制只有admin用户可以调用该函数。当其他用户调用时,会抛出异常。实际情况中,权限控制可能更加复杂,但该装饰器可以作为基础。

#### 幂等性装饰器

def idempotent(func):
    def wrapper(*args, **kwargs):
        if wrapper.last_value is not None:
            return wrapper.last_value
        result = func(*args, **kwargs)
        wrapper.last_value = result
        return result
    wrapper.last_value = None
    return wrapper

idempotent装饰器可以保证函数在多次调用时,返回的结果相同。用于需要幂等性保证的场景。

### 常见问题

1. 装饰器的执行顺序是什么样的?

装饰器的执行顺序是从上往下,从外到内。

例如,有3个装饰器分别为@A、@B、@C,并且从外到内的执行顺序为A -> B -> C,那么对于一个要被装饰的函数,执行顺序就是:

@A
@B
@C
def func():
    pass

先应用A装饰器,然后将其结果应用于B装饰器,最后将B+C的结果应用于func函数。

2. 装饰器会改变原函数的__name__等属性吗?

由于装饰器将原函数替换为新函数,一些原始属性(如__name__、__doc__、__module__等)可能会被改变。可以通过functools模块中的wraps函数修饰内部函数,将原函数的属性复制到包装函数中。

例如:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(func.__name__)
        return func(*args, **kwargs)
    return wrapper

functools.wraps会将原函数的__name__、__doc__等属性复制到wrapper函数中,避免属性被覆盖。

3. 装饰器是否有副作用?

由于装饰器不改变原始函数本身,仅在其前后增加一些操作,所以不会对原函数产生直接的副作用。但某些装饰器可能会对性能产生影响,或者改变函数的行为,因此在使用装饰器时应格外小心。