Python中的Decorators函数装饰器用法详解
在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. 装饰器是否有副作用?
由于装饰器不改变原始函数本身,仅在其前后增加一些操作,所以不会对原函数产生直接的副作用。但某些装饰器可能会对性能产生影响,或者改变函数的行为,因此在使用装饰器时应格外小心。
