Python装饰器----概览

来源:互联网 发布:linux文本编辑命令 编辑:程序博客网 时间:2024/06/09 13:56

装饰器是令代码包装函数或类的工具。装饰器显式地把包装器(wrapper)应用到函数或类上,让他们“参与”到装饰器的功能当中。装饰器用处很大:
应用场景例如,强调函数的运行要符合某种先决条件(例如确保身份验证通过)
或者保证函数运行结束后的的整理工作(例如,输出过滤或异常处理)。他们在被装饰方法或类本身上采取行动也有用处。例如,一个装饰器可能用一个 信号系统注册一个函数或者把一个URI注册到web应用中。本章提供了什么是装饰器,他们如何同Python函数和类交互的概览。列举了一些在Python标准库中的装饰器。最后提供了写装饰器并把他们附着到函数或类上面的介绍

一、理解装饰器

装饰器的核心,它是一个可调用对象(callable),它接收一个可调用对象,并返回一个可调用对象。
一个装饰器只不过是一个函数而已(或者其它的可调用对象,例如一个带有__call__ 方法的对象),它接收被装饰函数作为它的位置参数。
装饰器对这个参数进行一些处理,然后返回初始参数,或者返回其他可调用对象
因为在Python中函数是一级对象,它们能够像任何其他对象一样被传递给另一个函数。装饰器只是一个函数,它期待另一个函数传递过来,然后用它做一些事情。这听起来好像更混乱了。看下面一个非常简单的装饰器。它只是向被装饰的可调用对象追加一条docstring

def decorated_by(func):  func.__doc__ += '\nDecorated by decorated_by.'  return func

现在,看下面的这个普通函数:

def add(x, y):  """Return the sum of x and y."""  return x + y

函数的docstring是放在首行的字符串,在Python shell中,在这个函数上运行help你就会看到它。这就把装饰器应用到函数上:

def add(x, y):  """Return the sum of x and y."""  return x + yadd = decorated_by(add)

如果你运行help就会看到:

Help on function add in module __main__:add(x, y)Return the sum of x and y.Decorated by decorated_by.(END)

在这里装饰器对函数的__doc__ 进行了修改,然后返回初始函数对象

装饰器语法

Python2.5给装饰器引进了一个特殊的语法,在装饰器名字前加一个@字符,然后把这一句(不包含装饰器的方法签名)放到被装饰函数的声明上面。

下面是把decorated_by装饰器应用到add上的首选方法:

@decorated_bydef add(x, y):  """Return the sum of x and y."""  return x + y

再次注意@decorated_by没有方法签名。 装饰器被假定采用单一的,位置参数,即被装饰的函数 (在有些情况,你会看到一个方法签名,带有其它参数。本教程后面会讨论。)
这个语法允许把装饰器应用到函数声明处。阅读代码会很方便,立刻意识到装饰器器存在。代码可读性很重要。

装饰器使用顺序

装饰器什么时候起作用?在被装饰的可调用对象被创建后,一旦开启@语法,装饰器就会立刻起作用。首先,创建add函数,然后使用 decorated_by包装,一个值得注意的要点是,在一个可调用对象上可以使用多个装饰器 (就像多次调用包装函数一样).
然后,注意,如果你用@语法使用了多个装饰器,他们会从底部到顶部,按次序被应用。起初这可能违反直觉,但了解了Python解释器实际如何去做后这就讲得通了。
考虑下面应用了两个装饰器的函数:

@also_decorated_by@decorated_bydef add(x, y):  """Return the sum of x and y."""  return x + y

首先add函数被解释器创建。然后decorated_by 装饰器被应用。这个装饰器返回一个可调用对象(像所有装饰器做的那样),然后它被发送给also_decorated_by,它做同样的事情;稍后结果指定给add。记住,decorated_by 的应用与下面的语法等价:

add = decorated_by(add)

前面的两个装饰器的例子语法上等价于:

add = also_decorated_by(decorated_by(add))

在两种情况中,当我们阅读代码时also_decorated_by都是首先被看到。然而,跟装饰器按照从底部到顶部的顺序被应用出于相同原因,函数是从最内部到最外部被解析。相同的原则在起作用。在传统的函数调情况下,解释器必须首先解析内部函数调用来获取相关对象或值然后发送给外部函数调用。

首先 从‘decorated_by(add)`中获取一个返回值

add = also_decorated_by(decorated_by(add))

把这个返回值发送给‘also_decorated_by`

add = also_decorated_by(decorated_by(add))

对于一个装饰器,首先也正常的创建一个函数。

@also_decorated_by@decorated_bydef add(x, y):  """Return the sum of x and y."""  return x + y

然后 @decorated_by 装饰器被调用, add 函数被发送过来作为它的被装饰方法:

@also_decorated_by@decorated_bydef add(x, y):  """Return the sum of x and y."""  return x + y

@decorated_by 函数返回它自己的可调用对象(在这个例子中,是一个add的修改版本)。这就是最终发送给@also_decorated_by 的值。

@also_decorated_by@decorated_bydef add(x, y):  """Return the sum of x and y."""  return x + y

当使用装饰器时,记住从底部到顶部的应用顺序很重要(order does matter)

Where 装饰器应用在何处

标准库包含很多带有装饰器的模块,很多通用工具和框架在通用功能上使用它们。例如,你在一个类上写一个方法,调用时无需类的实例,你会用到 @classmethod 或者 @staticmethod 装饰器, 这是标准库的一部分。
mock模块 (用来进行单元测试,Python 3.3加入到标准库中) 允许使用 @mock.patch 或者@mock.patch.object 作为装饰器。
通用工具也用到装饰器。 Django 使用 @login_required 装饰器允许开发者指定一个用户必须登录后才能访问特定的页面,使用@permission_required来应用更多的特别许可。
Flask 使用@app.route 作为特定URI和函数之间的登记绑定。
Celery使用一个复杂的@task 装饰器标记一个函数为一个异步任务。这个装饰器实际返回了Task类的实例,这也就显示了如何用装饰器制作一个方便的API.

Why 为什么你要写一个装饰器

假如你有这样的诉求,“我要这个特定的,可重用的功能片段在这些个特定地方”那么,装饰器可以出色的完成任务。装饰器写的好的话,他们是模块化并且清晰明了。
装饰器的模块化(你可以从函数或类中很轻松地移除他们)使他们很理想地用于避免重复设置和拆解样板代码。类似的,因为装饰器同被装饰函数自身交互,他们善于在其他地方注册函数。

而且,装饰器非常明显。他们应用到所有可调用对象于需要他们的地方。对于可读性很有价值,因此也易于调试。应用了什么,应用在哪都非常明了。

When —-什么时候写装饰器

在Python应用和模块中,存在几种很好的写装饰器的使用场景。

额外功能Additional Functionality

在被装饰方法执行前或执行后添加额外功能,这可能是你想要写一个装饰器的最常见原因。包括的使用场景如,检查权限或者把一个函数的执行结果记录到指定地方

数据过滤或其他Data Sanitization or Addition

装饰器能够过滤传递给被装饰函数的参数的值,确保参数类型的一致,或者让一个值符合特定模式。例如,一个装饰器能确保发送给一个函数的的值符合特定类型,或者符合一些其他验证标准(很快你会看到这方面的例子,一个叫@requires_ints的装饰器)
装饰器还能够转换或者过滤从一个函数中返回的数据。一个很有价值的使用场景是你想要函数返回Python本地对象(像列表或者字典),但接收的是序列化格式(如JSON或者YAML)。一些装饰器提供额外数据给函数,通常以额外参数的形式。@mock.patch是这方面的一个例子。因为它提供它创建的mock对象作为额外的位置参数传递给函数

函数注册Function Registration

很多时候,在其它地方注册函数会很有用,例如在任务运行器中或者带有信号处理器的函数中注册一个任务,在任何系统中,一些外部输入或者路由机制决定了什么函数运行是函数注册的候选者。
最后一句话的原文是下面这样,what function runs是这个分句的主语,但这个主语让我疑惑:
Any system in which some external input or routing mechanism decides what function runs is a candidate for function registration.