一、装饰器基础
函数即对象
在python中函数可以作为参数传递,和任何其它对象一样如:str、int、float、list等等
python
def say_hello(name):
return f"Hello {name}"
def be_awesome(name):
return f"Yo {name}, together we're the awesomest!"
def greet_bob(greeter_func):
return greeter_func("Bob")
内部函数
在函数内部可以创建函数,这样的函数被称为内部函数:
python
def parent():
print("Printing from parent()")
def first_child():
print("Printing from first_child()")
def second_child():
print("Printing from second_child()")
second_child()
first_child()
在python在交互式shell中运行:
shell
python -i test.py
在使用 Python 时,尤其是在交互式 shell 中,其强大的自省(introspection)能力提供了极大的便利。自省是指对象在运行时能够了解自身属性的能力。
shell
>>> parent()
内部函数只有在父函数被调用时才会被定义,它的作用域仅限于父函数parent()
内:
>>> first_child()
Traceback (most recent call last):
...
NameError: name 'first_child' is not defined
函数作为返回值
函数可以作为返回值返回:
python
def parent(num):
def first_child():
return "Hi, I'm Elias"
def second_child():
return "Call me Ester"
if num == 1:
return first_child
else:
return second_child
通过变量保存返回的函数引用,可以像普通函数那样正常调用:
>>> first = parent(1)
>>> second = parent(2)
>>> first
<function parent.<locals>.first_child at 0x7f599f1e2e18>
>>> second
<function parent.<locals>.second_child at 0x7f599dad5268>
简单的装饰器
python
def decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
def say_whee():
print("Whee!")
say_whee = decorator(say_whee)
在shell中运行:
python
>>> from hello_decorator import say_whee
>>> say_whee()
# <function decorator.<locals>.wrapper at 0x7f3c5dfd42f0>
通过@符号来使用装饰器:
python
def decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@decorator
def say_whee():
print("Whee!")
传递参数:
python
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice
在shell中运行:
python
>>> from decorators import do_twice
>>> @do_twice
... def return_greeting(name):
... print("Creating greeting")
... return f"Hi {name}"
...
>>> return_greeting("Adam")
# Creating greeting
# Creating greeting
# 'Hi Adam'
保留原始信息
当函数被我们之前创建的装饰器装饰时,它的内部信息就会变得混乱,使用@functools.wraps
装饰器可以解决这个问题:
python
import functools
def do_twice(func):
@functools.wraps(func)
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
在shell中运行:
python
>>> from decorators import do_twice
>>> @do_twice
... def say_whee():
... print("Whee!")
...
>>> say_whee
<function say_whee at 0x7ff79a60f2f0>
>>> say_whee.__name__
'say_whee'
>>> help(say_whee)
Help on function say_whee in module whee:
say_whee()
可以看到经过@functools.wraps
装饰器后,say_whee
函数的元数据被保留下来,包括函数名、文档字符串等。
二、更复杂的装饰器
嵌套装饰器
装饰器可以堆叠用于一个函数:
python
>>> from decorators import debug, do_twice
>>> @debug
... @do_twice
... def greet(name):
... print(f"Hello {name}")
带参数的装饰器
我们可以定义能够接收参数的装饰器,如下:
python
from decorators import repeat
@repeat(num_times=4)
def greet(name):
print(f"Hello {name}")
greet("World")
实现方法如下:
python
import functools
# ...
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
拆分讲解上面的代码:
首先decorator_repeat
部分的行为,就和我们之前定义的普通装饰器一样:
python
...
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
...
外层由repeat()
函数接收参数,并返回wrapper_repeat
装饰器的引用。
在装饰器中跟踪状态
可以实现装饰器对象中添加属性来追踪函数调用次数:
python
import functools
def count(func):
@functools.wraps(func)
def warp(*args, **kwargs):
warp.num_calls += 1
print(f"Call {warp.num_calls} of {func.__name__}()")
return func(*args, **kwargs)
warp.num_calls = 0
return warp
@count
def hello():
print("Hello")
hello()
hello()
hello()
类装饰器
有两种不同的方式可以在类中使用装饰器,第一种方法和我们上面学到的在函数中使用装饰器的方法非常类似。
Python有很多内置的装饰器很常用:
@classmethod
@staticmethod
@property
在python中维护状态的典型方法就是使用类。之前实现的count
函数可以使用类装饰器重写。为了使实例可调用,需要实现.__call__()
方法:
python
>>> class Counter:
... def __init__(self, start=0):
... self.count = start
... def __call__(self):
... self.count += 1
... print(f"Current count is {self.count}")
每次调用实例都会执行.__call__()
方法:
python
>>> counter = Counter()
>>> counter()
Current count is 1
>>> counter()
Current count is 2
>>> counter.count
实现装饰器类的典型方法是应该实现.__init__()
和.__call__()
python
import functools
# ...
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__}()")
return self.func(*args, **kwargs)
使用方法:
python
>>> from decorators import CountCalls
>>> @CountCalls
... def say_whee():
... print("Whee!")
...
>>> say_whee()
# Call 1 of say_whee()
# Whee!
>>> say_whee()
# Call 2 of say_whee()
# Whee!
>>> say_whee.num_calls
# 2