python装饰器
函数也是对象
在python中,function是一级对象,这也就意味着function可以作为arguments一样传递,就像其它的对象一样(字符串,整型变量,浮点型变量,列表等等)。当函数名像其它类型的变量一样作为另一个函数的参数被传递时,实现了函数的嵌套调用。
def say_hello(name):
return f"Hello {name}"
def be_awesome(name):
return f"Yo {name}, you are awesome!"
def greet_bob(greeter_func):
return greeter_func("Bob")
print(greet_bob(say_hello))
print(greet_bob(be_awesome))
Hello Bob
Yo Bob, you are awesome!
函数内部定义的函数成为inner function, 内部函数。内部函数只存在在局部空间,也就是说只能在定义的函数中被调用,下面的例子就说明了这一点。
def parent():
print('The parent() function')
def first_child():
print('The first_child() function')
def second_child():
print('The second_child() function')
second_child()
first_child()
parent()
The parent() function
The second_child() function
The first_child() function
函数名也可以用作返回参数。这时,内部定义的函数也可以被外部访问到,可以当作一个普通函数调用,也就是要加上()。
def parent(num):
def first_child():
return "HI I am Emma"
def second_child():
return "HI I am Lin"
if num == 1:
return first_child
else:
return second_child
first_child = parent(1)
second_child = parent(2)
print(first_child()) # 注意不是print(first_child)
print(second_child())
HI I am Emma
HI I am Lin
print(first_child)
<function parent.<locals>.first_child at 0x1115e74d0>
简单装饰器
理解了函数是python对象,函数名可以作为参数传来传去这个点后,我们下面来个稍微有点难度的例子,let’s go!
def my_decorator(func):
def wrapper():
print("Before the function is called")
func()
print("After the function is called")
return wrapper
def say_whee():
print("whee")
say_whee = my_decorator(say_whee)
say_whee()
Before the function is called
whee
After the function is called
say_whee = my_decorator(say_whee)这一句就是应用了所谓的函数装饰器。事实上,这里把say_whee函数名作为my_decorator的输入参数,之后将返回的函数名wrapper赋予了最左侧的say_whee变量。这时候,最左侧的say_whee实际上指向了my_decorator的内部函数wrapper()。
简单滴说,就是“装饰器“是封装了一系列函数的行为,将被装饰的函数嵌入到这些行为中。(decorators wrap a function, modifying its behavior)”
下面的例子中,函数被限制在了工作时间执行(7到22点),首先看看如果用普通的函数定义:
from datetime import datetime
def not_during_the_night(func):
def wrapper():
if 7 <= datetime.now().hour < 22:
func()
else:
pass
return wrapper
def say_whee():
print("Whee!")
say_whee = not_during_the_night(say_whee)
say_whee()
Whee!
装饰器@ 语法给上面的例子提供了更加简介的解决方案。值得注意的是,装饰器构造函数一定要返回行为定义内部函数的函数名,装饰器使用的时候是在新函数定义的前一行,用@符号后面跟着装饰器函数名,不加()。例如:
@not_during_the_night
def say_whee_in_work_hours():
print("Whee")
say_whee_in_work_hours()
Whee
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
@do_twice
def say_whee():
print("Whee")
say_whee()
Whee
Whee
被修饰函数有参数的情况
上面的例子中,被修饰的函数都是没有输入和输出参数的。我们先来看看下面的这个例子:
@do_twice
def greet(name):
print(f"Hello {name}")
greet("Alice")
报错:greet() missing 1 required positional argument: ‘name’
这是什么情况?回忆一下装饰器的工作原理,在函数执行的时候,实际上是传给了do_twice装饰器函数,在这个函数内部再执行greet这个函数,但是这时greet函数没有输入参数。怎么解决问题?这时应该用到python的可变参数。
def do_twice_with_args(func):
def wrapper_do_twice(*args,**kwargs):
func(*args,**kwargs)
func(*args,**kwargs)
#return wrapper_do_twice #报错:'NoneType' object is not callable
return wrapper_do_twice
@do_twice_with_args
def greet(name):
print(f"Hello {name}")
greet("Alice")
Hello Alice
Hello Alice
如果函数有返回值呢?
@do_twice_with_args
def greet_back(name):
return f"Hello {name}"
print(greet_back("Ben"))
None
返回的是None。显然装饰函数运行过程中“吞”掉了greet_back函数的输出,就在装饰器内部函数wrapper_do_twice()内部,调用传入的函数时,没有返回值。我们修改一下do_twice_with_args的定义,在wrapper_to_twice函数后面添加一个return语句。
事实上,之前返回的None是python函数在用户没有定义return语句时默认返回的值。
def do_twice_with_args(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
@do_twice_complex
def greet_back(name):
print('Hello, Alice')
return f"Hello, {name}"
def no_return():
print()
print('default return should be ')
hi = greet_back("Ben")
print(hi)
ret = no_return()
print(ret)
Hello, Alice
Hello, Alice
Hello, Ben
default return should be
None
通常我们用__定义对象的内部变量和内部函数,python的这一机制便于查看对象的基本情况,还可以用内置的help函数查看对应的帮助文档。例如:
print("print.__name__: "print.__name__)
print(help(print))
print
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
None
但是在装饰器语法装饰后,会让被装饰函数丧失这个特性,有的时候会给代码编写和测试带来麻烦,如何修复这个问题?
可以用functools工具包解决。例如:
print('greet_back.__name__: '+greet_back.__name__)
print(help(greet_back))
print()
print("import functools toolbox to fix this. ")
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
@do_twice
def greet(name):
print('Hi! ')
return f"Hello, {name}"
print(greet("Ti-Ya"))
print('greet.__name__: '+greet.__name__)
greet_back.__name__: wrapper_do_twice
Help on function wrapper_do_twice in module __main__:
wrapper_do_twice(*args, **kwargs)
None
import functools toolbox to fix this.
Hi!
Hi!
Hello, Ti-Ya
greet.__name__: greet
到这里,python装饰器的定义和基本使用就讲完了。下面给出实际中的例子。
装饰器的应用
装饰器函数代码的一般结构
首先来总结下一个装饰器函数的一般写法:
import functools
def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Do something before
value = func(*args, **kwargs)
# Do something after
return value
return wrapper_decorator
记时装饰器
记录函数运行时间
## 记时装饰器
import functools
import time
def timer(func):
"""Print the run time of the decorated function."""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Fininshed {func.__name__} in {run_time:.4f} secs")
return value
return wrapper_timer
import tensorflow as tf
import numpy as np
@timer
def tensor_computation():
a = tf.constant(np.random.randn(3,3))
b = tf.constant(np.random.randn(3,3))
c = tf.matmul(a,b)
return c
tensor_computation()
Fininshed tensor_computation in 0.0002 secs
<tf.Tensor: shape=(3, 3), dtype=float64, numpy=
array([[-0.67434088, 1.34905892, 0.59164407],
[-2.14192767, 1.13678956, -0.48185652],
[-0.6749793 , 1.35129415, -1.46809712]])>
debug模式装饰器
可以定义debug装饰器时对函数的执行情况进行检查,这里给个简单的例子。repr()是python的内置函数,和str()用法类似但又有相同。详见:https://www.geeksforgeeks.org/str-vs-repr-in-python/
此外,f""字符串中的!r代表按照两边带引号的字符串进行打印。关于f-string更多的说明和帮助在这里:
https://www.python.org/dev/peps/pep-0498/#s-r-and-a-are-redundant
import functools
def debug(func):
"""Print the function signature and return value"""
def wrapper_debug(*args,**kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k,v in kwargs.items()]
signature = ','.join(args_repr+kwargs_repr)
print(f"Calling {func.__name__}({signature})")
value = func(*args, **kwargs)
print(f"{func.__name__!r} returned {value!r}")
return value
return wrapper_debug
@debug
def make_greeting(name, age=None):
if age is None:
return f"Howdy {name}!"
else:
return f"Whoa {name}! {age} already, you are growing up!"
make_greeting("Benjamin")
make_greeting("Richard", age=112)
print()
rname = "Neptune"
print(f"{rname!r} is a cute boy!")
Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'
Calling make_greeting('Richard',age=112)
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'
'Neptune' is a cute boy!