装饰器的目的是什么(为什么使用它们)?

Eri*_*pur 5 python python-3.x

我一直在学习和试验装饰器。我理解它们的作用:它们允许您通过向现有函数添加功能而不更改它们来编写模块化代码。

我找到了一个很好的线程,它通过解释这里的所有来龙去脉,真正帮助我学习了如何做到这一点:如何制作函数装饰器链?

但是这个线程(以及我看过的其他资源)缺少的是为什么我们需要装饰器语法?为什么我需要嵌套函数来创建装饰器?为什么我不能只使用一个现有的函数,编写另一个具有一些额外功能的函数,然后将第一个函数输入到第二个函数中以使其执行其他操作?

仅仅是因为这是约定吗?我错过了什么?我认为我的经验不足在这里表现出来。

has*_*raf 7

我将尽力保持简单并用示例进行解释。我经常做的事情之一是测量我构建然后在 AWS 上发布它们的 API 所花费的时间。

由于这是一个非常常见的用例,我为它创建了一个装饰器。

def log_latency():
def actual_decorator(f):
    @wraps(f)
    def wrapped_f(*args, **kwargs):
        t0 = time()
        r = f(*args, **kwargs)
        t1 = time()
        async_daemon_execute(public_metric, t1 - t0, log_key)
        return r
    return wrapped_f

return actual_decorator
Run Code Online (Sandbox Code Playgroud)

现在,如果有任何我想测量延迟的方法,我只需用所需的装饰器对其进行注释即可。

@log_latency()
def batch_job_api(param):
    pass
Run Code Online (Sandbox Code Playgroud)

假设您想编写一个安全 API,该 API 仅在您发送具有特定值的标头时才起作用,然后您可以为其使用装饰器。

def secure(f):
@wraps(f)
def wrapper(*args, **kw):
    try:
        token = request.headers.get("My_Secret_Token")
        if not token or token != "My_Secret_Text":
            raise AccessDenied("Required headers missing")
    return f(*args, **kw)
return wrapper
Run Code Online (Sandbox Code Playgroud)

现在只写

@secure
def my_secure_api():
    pass
Run Code Online (Sandbox Code Playgroud)

我还一直使用上述语法来处理 API 特定的异常,如果一个方法需要数据库交互而不是获取连接,我使用 @session 装饰器,它告诉该方法将使用数据库连接,并且您不需要处理一个你自己。

我显然可以通过编写一个函数来检查标头或打印 AWS 上的 API 所花费的时间来避免这种情况,但这看起来有点丑陋且不直观。

对此没有约定,至少我不知道它们,但它确实使代码更具可读性且更易于管理。大多数 IDE 还具有不同的颜色注释语法,这使得更容易理解和组织代码。

所以,我想这可能只是因为缺乏经验;如果您开始使用它们,您将自动开始知道在哪里使用它们。


Ste*_*ski 4

来自PEP 318 -- 函数和方法的装饰器(我自己还强调了):

动机

当前对函数或方法应用转换的方法将实际转换放在函数体之后。对于大型函数,这将函数行为的关键组件与函数外部接口的其余部分的定义分开。例如:

def foo(self):
    perform method operation 
foo = classmethod(foo) 
Run Code Online (Sandbox Code Playgroud)

对于较长的方法,这会变得不太可读。对于概念上的单个声明来说,将函数命名三次似乎也不太符合 Python 风格。此问题的解决方案是将方法的转换移至更接近方法自身的声明。新语法的目的是取代

def foo(cls):
    pass 
foo = synchronized(lock)(foo) 
foo = classmethod(foo)
Run Code Online (Sandbox Code Playgroud)

另一种方法是将装饰放在函数的声明中:

@classmethod
@synchronized(lock)
def foo(cls):
    pass
Run Code Online (Sandbox Code Playgroud)