📅  最后修改于: 2023-12-03 15:04:37.233000             🧑  作者: Mango
装饰器是Python中一个很重要的概念,它可以用来增强一个函数或者类的功能,而不需要修改原本的代码。在Python中,装饰器本质上是一个返回函数或者对象的函数(或者类),而且它满足一些特定的约束条件。在本文中,我们将会介绍装饰器的基本用法和原理,以及如何在实际的开发中使用装饰器。
在Python中,装饰器是一个函数或者类,它可以用来修饰另一个函数或者类。比如,我们可以定义一个装饰器来为某个函数增加打印日志的功能:
def log(func):
def wrapper(*args, **kwargs):
print(f'{func.__name__} has been called')
return func(*args, **kwargs)
return wrapper
@log
def foo():
print('Hello, world!')
foo()
这段代码中,我们定义了一个log函数,它接受一个函数作为参数,并且返回一个新的函数wrapper。这个wrapper函数所做的事情是首先打印一个日志,然后调用原函数(即被修饰的函数)并返回结果。由于wrapper函数中用到了被修饰函数的参数和返回值,因此我们在wrapper函数中定义了*args和**kwargs,它们可以接受任意数量和类型的参数。
在修饰完成之后,我们就可以使用@log语法来应用这个装饰器。这样,在我们调用foo函数时,它就会先打印一条日志再介绍了实际的函数。运行结果如下:
foo has been called
Hello, world!
在Python中,装饰器本质上是一个重载了__call__方法的类或者一个返回函数的函数。具体来说,当使用@decorator语法修饰某个函数或者类时,Python会把这个函数或者类当做参数传递给装饰器函数,然后把装饰后的函数或者类替代原本的函数或者类。这个过程本质上就是一个闭包,因为它涉及到了内部函数和外部变量的引用和传递。
我们可以通过手动调用装饰器来还原这个过程。比如,我们可以将上述代码改为如下形式:
def foo():
print('Hello, world!')
foo = log(foo)
foo()
这个例子中,我们手动调用了log装饰器来修饰foo函数,然后将修饰后的函数赋值给原有的foo变量。这样,你就可以看到手动调用装饰器和使用@decorator语法本质上是一样的。
在实际开发中,装饰器被广泛使用在许多场景中。比如,我们可以使用装饰器来检查函数的参数类型、缓存函数的结果、测量函数的执行时间、限制函数的访问权限等等。下面是一些具体的例子:
from functools import wraps
def typed(func):
@wraps(func)
def wrapper(*args, **kwargs):
for (a, t) in zip(args, func.__annotations__.values()):
if not isinstance(a, t):
raise TypeError(f'{a} is not {t}')
return func(*args, **kwargs)
return wrapper
@typed
def foo(a: int, b: str):
print(a, b)
foo(1, 'test')
这个例子中,我们定义了一个叫做typed的装饰器,它用于检查函数的参数类型是否正确。具体来说,它首先获取目标函数的注解(也就是参数列表上的类型标注),然后遍历参数和类型,逐个检查参数的类型是否正确。如果发现参数类型不符合要求,就会抛出TypeError异常。最后,如果一切正常,就会调用原函数并返回结果。
from functools import wraps
def cached(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cached
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(15))
这个例子中,我们定义了一个叫做cached的装饰器,它用于缓存目标函数的结果。具体来说,我们通过一个字典来保存函数参数和结果之间的映射关系。如果参数已经出现过,那么就直接返回对应的结果。否则,我们就调用原函数来计算结果,并把结果放入cache字典中。最后,我们返回计算得到的结果。
import time
def timeit(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__} took {end - start} seconds')
return result
return wrapper
@timeit
def foo():
time.sleep(1)
foo()
这个例子中,我们定义了一个叫做timeit的装饰器,它用于测量目标函数的执行时间。具体来说,我们通过time模块来获取函数开始和结束的时间,然后计算二者之差,最后打印结果。为了避免在wrapper函数中覆盖原函数的属性,我们使用了functools库中的wraps函数来对wrapper进行修饰。
class Role:
def __init__(self, name):
self.name = name
def check_permission(self, perm):
if perm == 'read':
return True
return False
def require_permission(perm):
def decorator(cls):
class Wrapper:
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
def __getattr__(self, attr):
if attr == perm:
if self.wrapped.check_permission(perm):
return getattr(self.wrapped, attr)
raise ValueError(f'permission denied: {perm}')
return getattr(self.wrapped, attr)
return Wrapper
return decorator
@require_permission('read')
class MySecret:
def __init__(self):
self.secret = 'my secret data'
def read(self):
return self.secret
ms = MySecret()
print(ms.read())
这个例子中,我们定义了一个名为require_permission的装饰器,它用于限制类的某个方法的访问权限。具体来说,我们首先定义了一个Role类,它包含一个check_permission方法,用于检查某个权限是否被授权。然后,我们定义了一个装饰器,它接受一个要限制的权限作为参数,并且返回一个新的类。这个新类叫做Wrapper,它包含一个init方法和一个getattr方法。init方法用于创建原有类的实例,getattr方法则用于检查方法调用是否符合权限要求。如果发现权限不足,就会抛出ValueError异常。最后,我们使用@decorator语法来应用这个装饰器,并且调用限制了权限的方法来验证结果。
装饰器是Python中的一个重要概念,它可以用来增强函数或者类的功能,而不需要修改原本的代码。在本文中,我们介绍了装饰器的基本用法和原理,以及如何在实际开发中使用装饰器来检查参数类型、缓存结果、测量执行时间、限制访问权限等等。如果你希望深入了解这个话题,可以参考Python官方文档或者相关的教程和博客。