geekdoc-python-zh/docs/pythoncentral/python-decorators-overview.md

273 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python 装饰者概述
> 原文:<https://www.pythoncentral.io/python-decorators-overview/>
Python 中的 Decorators 看起来很复杂,但是它们非常简单。你可能见过他们;它们是以'`@`'开头的函数定义前的奇数位,例如:
```py
def decorator(fn):
def inner(n):
return fn(n) + 1
return inner
@ decorator
def f(n):
return n+1
```
请注意名为 decorator 的函数;它将一个函数作为参数,定义并返回一个新函数,该函数使用传递给它的函数。几乎所有的装饰者都有这种模式。`@decorator`符号只是一种调用现有函数的特殊语法,将新函数作为参数传递,并使用返回值替换新函数。
在上面的例子中,用`f`作为参数调用 decorator它返回一个新函数来代替`f`。同样的效果可能不那么简洁地写出来:
```py
def decorator(fn):
def inner(n):
return fn(n) + 1
return inner
def f(n):
返回 n + 1
f =装饰者(f)
```
所有的`@`符号都是为了使语法更加简洁。
## 查看工作中的 Python 装饰者
下面是一个简单的例子,它的输出说明了装饰器是如何工作的。
```py
def wrap_with_prints(fn):
# This will only happen when a function decorated
# with @wrap_with_prints is defined
print('wrap_with_prints runs only once')
def wrapped():
#每次在
之前都会发生这种情况#被修饰的函数被调用
print('即将运行%s' % fn__name__)
#这里是包装器调用装饰函数
fn()
#这将在每次
#装饰函数被调用
print('Done running %s' % fn__name__)
包装退货
@ wrap _ with _ prints
def func _ to _ decorate():
print('运行被修饰的函数。')
func _ to _ decoration()
```
运行该示例时,输出将如下所示:
```py
wrap_with_prints runs only once
About to run func_to_decorate
Running the function that was decorated.
Done running func_to_decorate
```
注意,装饰器(`wrap_with_prints`)只在创建装饰函数时运行一次,但是内部函数(`wrapped`)会在每次运行`func_to_decorate`时运行。
# 一个几乎实际的例子
Python 的函数就像任何其他 Python 对象一样;您可以将它们赋给变量,在函数调用中将它们作为参数传递,从其他函数返回它们,将它们放入列表和字典中,等等。(大家说 Python 有一级函数就是这个意思。)装饰器是利用这一事实来提供有用功能的一种简洁方式。
例如,假设我们有以下代码(您会将其识别为 [fizzbuzz](http://www.codinghorror.com/blog/2007/02/why-cant-programmers-program.html "fizzbuzz") ):
```py
def fizz_buzz_or_number(i):
''' Return "fizz" if i is divisible by 3, "buzz" if by
5, and "fizzbuzz" if both; otherwise, return i. '''
if i % 15 == 0:
return 'fizzbuzz'
elif i % 3 == 0:
return 'fizz'
elif i % 5 == 0:
return 'buzz'
else:
return i
对于范围(131)中的 I:
print(fizz _ buzz _ or _ number(I))
```
然后,假设我们想要记录函数的参数和返回值以便调试。(有更好的调试方法,您应该使用它们,但这对我们来说是一个有用的例子。)我们可以在函数中添加日志记录语句,但是如果我们有更多的函数,那就太麻烦了,而且更改正在调试的函数很可能会引入其他错误,这也是必须要调试的。我们需要一个保持函数完整的通用方法。
输入装饰师来拯救这一天!我们可以编写一个装饰函数来为我们记录日志:
```py
def log_calls(fn):
''' Wraps fn in a function named "inner" that writes
the arguments and return value to logfile.log '''
def inner(*args, **kwargs):
# Call the function with the received arguments and
# keyword arguments, storing the return value
out = apply(fn, args, kwargs)
#在日志文件
中写入一行函数名
#参数和返回值,并打开(' logfile.log '' a ')作为日志文件:
logfile.write(
')用 args %s 和 kwargs %s 调用%s,返回% s \ n“%
(fn__name__argskwargsout))
#返回返回值
返回出
返回内
```
然后,我们需要做的就是将我们的装饰器添加到`fizz_buzz_or_number`的定义中:
```py
@log_calls
def fizz_buzz_or_number(i):
# Do something
```
运行该程序将生成如下所示的日志文件:
```py
fizz_buzz_or_number called with args (1,) and kwargs {}, returning 1
fizz_buzz_or_number called with args (2,) and kwargs {}, returning 2
fizz_buzz_or_number called with args (3,) and kwargs {}, returning fizz
fizz_buzz_or_number called with args (4,) and kwargs {}, returning 4
fizz_buzz_or_number called with args (5,) and kwargs {}, returning buzz
# Do something
fizz_buzz_or_number called with args (28,) and kwargs {}, returning 28
fizz_buzz_or_number called with args (29,) and kwargs {}, returning 29
fizz_buzz_or_number called with args (30,) and kwargs {}, returning fizzbuzz
```
同样,有更好的调试方法,但这是装饰器可能用途的一个很好的例子。
# 更高级的用途
上面的装饰者是简单的例子;还有更高级的可能性。
## 多个装饰者
你可以连锁装修。例如,您可以使用 decorators 将函数返回的文本包装在 HTML 标签中——尽管我不建议这样做;请改用模板引擎。在任何情况下:
```py
def b(fn):
return lambda s: '<b>%s</b>' % fn(s)
def em(fn):
return lambda s:'<em>% s</em>' % fn(s)
@ b
@ em
def greet(name):
你好% s% name
```
然后,调用`greet('world')`将返回:
```py
<b><em>Hello, world!</em></b>
```
请注意,装饰器是按照从下到上的顺序应用的;函数先被`em`包装,然后结果被`b`包装。没有按照正确的顺序放置装饰器会导致混乱的、难以追踪的错误。
## 有争论的装饰者
有时,您可能希望将参数与要装饰的新函数一起传递给装饰函数。例如,您可能想要将前一个示例中的 HTML 标记包装 decorators b 和 em 抽象成一个通用的`tag_wrap` decorator它将标记作为一个额外的参数这样我们就不必为每个 HTML 标记都有单独的 decorator。可惜你做不到。然而你可以做一些看起来像是给装饰器传递参数的事情并且有类似的效果:你可以写一个接受参数的函数,然后*返回*一个生成器函数:
```py
def tag_wrap(tag):
def decorator(fn):
def inner(s):
return '<%s>%s' % (fn(s), tag)
return inner
return decorator
@ tag _ wrap(' b ')
@ tag _ wrap(' em ')
def greet(name):
' Hello%s'% name
打印(问候('世界'))
```
这个例子的结果与上一个例子的结果相同。
让我们从外向内回顾一下`tag_wrap`做了什么。`tag_wrap`是一个接受标签并返回接受函数的函数装饰器的函数。当传递一个函数时decorator 返回一个名为`inner`的函数,该函数接受一个字符串,将该字符串传递给传递给 decorator 的函数,并将结果包装在传递给`tag_wrap`的任何标签中。
这是一系列令人难以理解的定义,但是从一个库作者的角度来考虑它:对于不得不这么做的人来说这很复杂,但是对于包含它的库的用户来说,这非常简单。
# 现实世界中的装饰者
许多库的作者利用 decorators 来为库用户提供简单的方法将复杂的功能添加到他们的代码中。例如web 框架 [Django](https://www.djangoproject.com/ "Django Project") 使用装饰器`login_required`,使视图需要用户认证——这无疑是以如此复杂的方式扩展功能的一种便捷方式。
另一个 Python web 框架 [CherryPy](http://www.cherrypy.org/ "CherryPy Project") 更进一步,它的工具处理认证、静态目录、错误处理、缓存等等。 [CherryPy](http://www.cherrypy.org/ "CherryPy") 提供的每一个工具都可以用一个配置文件来配置,通过直接调用工具,或者通过使用 decorators其中一些“接受参数”比如:
```py
@tools.staticdir(root='/path/to/app', dir='static')
def page(self):
# Do something
```
它定义了一个名为“static”的静态目录位于“/path/to/app”。
虽然这些 decorator 的设计是为了简化它们的使用,即使对于那些不完全理解它们如何工作的人来说也是如此,但是对 decorator 的全面理解将允许您更灵活、更智能地使用它们,并在适当的时候创建自己的 decorator从而节省您的开发时间和精力并简化维护。