Files
python3-cookbook/source/c09/p01_put_wrapper_around_function.rst
2015-12-28 19:34:04 +08:00

102 lines
3.0 KiB
ReStructuredText
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.

============================
9.1 在函数上添加包装器
============================
----------
问题
----------
你想在函数上添加一个包装器,增加额外的操作处理(比如日志、计时等)。
----------
解决方案
----------
如果你想使用额外的代码包装一个函数,可以定义一个装饰器函数,例如:
.. code-block:: python
import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
下面是使用装饰器的例子:
.. code-block:: python
>>> @timethis
... def countdown(n):
... '''
... Counts down
... '''
... while n > 0:
... n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912
>>>
----------
讨论
----------
一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。
当你像下面这样写:
.. code-block:: python
@timethis
def countdown(n):
pass
跟像下面这样写其实效果是一样的:
.. code-block:: python
def countdown(n):
pass
countdown = timethis(countdown)
顺便说一下,内置的装饰器比如 ``@staticmethod, @classmethod,@property`` 原理也是一样的。
例如,下面这两个代码片段是等价的:
.. code-block:: python
class A:
@classmethod
def method(cls):
pass
class B:
# Equivalent definition of a class method
def method(cls):
pass
method = classmethod(method)
在上面的 ``wrapper()`` 函数中,
装饰器内部定义了一个使用 ``*args````**kwargs`` 来接受任意参数的函数。
在这个函数里面调用了原始函数并将其结果返回,不过你还可以添加其他额外的代码(比如计时)。
然后这个新的函数包装器被作为结果返回来代替原始函数。
需要强调的是装饰器并不会修改原始函数的参数签名以及返回值。
使用 ``*args````**kwargs`` 目的就是确保任何参数都能适用。
而返回结果值基本都是调用原始函数 ``func(*args, **kwargs)`` 的返回结果其中func就是原始函数。
刚开始学习装饰器的时候,会使用一些简单的例子来说明,比如上面演示的这个。
不过实际场景使用时,还是有一些细节问题要注意的。
比如上面使用 ``@wraps(func)`` 注解是很重要的,
它能保留原始函数的元数据(下一小节会讲到),新手经常会忽略这个细节。
接下来的几个小节我们会更加深入的讲解装饰器函数的细节问题,如果你想构造你自己的装饰器函数,需要认真看一下。