📜  Python中的装饰器(1)

📅  最后修改于: 2023-12-03 15:04:37.233000             🧑  作者: Mango

Python中的装饰器

装饰器是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官方文档或者相关的教程和博客。