TAG : **PYTHON**
STATE : **FINISHED**
1. 函数
Reference: https://www.yuque.com/petwan/toolchain/data-classes
在理解装饰器之前,先看一个函数的实例
def add_one(number):
return number + 1
add_one(2)
在python中,万物都是对象,函数也是,函数作为对象可以赋值给一个变量,可以作为元素添加到集合对象中,也可以作为参数传递给其他函数,同时还可以当作函数的返回值,以上这些特性就是第一类对象所特有的。
1.1 函数是对象
函数作为一个对象,拥有对象模型的三个通用属性:
- id
- type
- value ```python def add_one(number): return number + 1
id(add_one) # id number
type(add_one) #
foo = add_one # 赋值给另一个变量,函数并不会被调用,仅仅是在函数对象上绑定了一个新的名字而已
foo(2) 等同于 add_one(2)
<a name="Sj1j3"></a>
## 1.2 高阶函数
函数接受一个或多个函数作为输入/输出时,我们称之为高阶函数,典型的高阶函数是map函数。
```python
def foo(text):
return len(text)
lens = map(foo, ['hello', 'world!!!!'])
print(list(lens))
# [5, 9]
# ----------------------------
# map函数的作用相当于
# [foo(i) for i in ["hello","world"]]
1.3 函数嵌套
Python允许函数嵌套,但是嵌套的函数不能在函数外部访问,只能在函数内部使用。
def parent():
print("Printing from the parent() function")
def first_child():
print("Printing from the first_child() function")
def second_child():
print("Printing from the second_child() function")
second_child()
first_child()
parent()
# ----------------
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function
1.4 返回函数
def parent(num):
def first_child():
return "Hi, I am Emma"
def second_child():
return "Call me Liam"
if num == 1:
return first_child
else:
return second_child
first = parent(1)
second = parent(2)
first()
# "Hi, I am Emma"
second()
# "Call me Liam"
1.5 实现call的类
对于自定义的类,如果实现了call方法,那么该类的实例对象的行为就是一个函数,是一个可以被调用callable的对象
class Add:
def __init__(self, n):
self.n = n
def __call__(self):
return self.n + x
add = Add(1)
add(4)
# 5
2. 简单的装饰器
下面先实现一个简单的装饰器
def my_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 = my_decorator(say_whee)
say_where()
# ---------------------
Something is happening before the function is called.
Whee!
Something is happening after the function is called.
可以看到,利用my_decorator函数实现了对某个func的调用。
接下来,我们再看一个实例
from datetime import datetime
def not_during_the_night(func):
def wrapper():
if 7 <= datetime.now().hour < 22:
func()
else:
pass # Hush, the neighbors are asleep
return wrapper
def say_whee():
print("Whee!")
say_whee = not_during_the_night(say_whee)
如果在22点到7点之间调用函数,什么都不执行。
2.1 Syntactic Sugar
上面的例子中,如果要实现对say_whee()函数的调用,我们需要在对应的函数中加入say_whee作为函数输入。
为了简化,我们利用@语法进行改写:
def my_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
@my_decorator
def say_whee():
print("Whee!")
其中,@my_decorator
等效于say_whee = my_decorator(say_whee)
。
可以将自定义的装饰器统一放到一个module
中,通过import
的形式进行使用。
# decorators.py
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
from decorators import do_twice
@do_twice
def say_whee():
print("Whee!")
say_whee()
# -----------------
Whee!
Whee!
2.2 含参装饰函数
如果某个函数需要参数输入,对应的装饰函数如何写?
借助args, *kwargs实现
# decorators.py
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice
from decorators import do_twice
@do_twice
def say_whee(name):
print("Hello {}".format(name))
say_whee("world")
# -------------------
Hello World
Hello World
2.3 从装饰函数返回值
如果想要让装饰函数返回值,如何实现?
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
a = return_greeting("world")
print(a)
# ----------------------------
Creating greeting
Creating greeting
Hi world
2.4 函数元信息
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
print(return_greeting.__name__)
# 显示结果不再是 return_greeting,而是 wrapper_do_twice
借助 functools
保留函数的元信息
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 return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
print(return_greeting.__name__)
# return_greeting
3. 举个例子
这里给出一个简单的实例
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
基于上面这个模块,可以构建更为复杂的装饰函数。
3.1 计时装饰函数
我们创建一个计时的装饰器,用于计算程序的耗时,并打印在console中。
import functools
import time
def timer(func):
"""
Print the runtime of the decorated function
"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter() # 1
value = func(*args, **kwargs)
end_time = time.perf_counter() # 2
run_time = end_time - start_time # 3
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return value
return wrapper_timer
@timer
def waste_some_time(num_times):
for _ in range(num_times):
sum([i**2 for i in range(10000)])
waste_some_time(1)
上面的装饰器实现了计算程序耗时,这里仅作提示,实际上可以借助timeit模块进行程序耗时查看。
3.2 调试代码
import functools
def debug(func):
"""Print the function signature and return value"""
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
args_repr = [repr(a) for a in args] # 1
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] # 2
signature = ", ".join(args_repr + kwargs_repr) # 3
print(f"Calling {func.__name__}({signature})")
value = func(*args, **kwargs)
print(f"{func.__name__!r} returned {value!r}") # 4
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")
# --------------------
Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'
也可以将该装饰函数应用到标准函数中
import math
# Apply a decorator to a standard library function
math.factorial = debug(math.factorial)
def approximate_e(terms=18):
return sum(1 / math.factorial(n) for n in range(terms))
3.3 注册插件
import random
PLUGINS = dict()
def register(func):
"""
Register a function as a plug-in
"""
PLUGINS[func.__name__] = func
return func
@register
def say_hello(name):
return f"Hello {name}"
@register
def be_awesome(name):
return f"Yo {name}, together we are the awesomest!"
def randomly_greet(name):
greeter, greeter_func = random.choice(list(PLUGINS.items()))
print(f"Using {greeter!r}")
return greeter_func(name)
print(randomly_greet("Alice"))
这种方法的好处在于用户无需维护插件列表,仅需要使用@register进行注册即可。
3.4 嵌套
可以对某个函数进行多层装饰,按照从代码上到下的顺序进行嵌套
from decorators import debug, do_twice
@debug
@do_twice
def greet(name):
print(f"Hello {name}")
greet("Eva")
# --------------
Calling greet('Eva')
Hello Eva
Hello Eva
'greet' returned None
如果修改嵌套顺序
from decorators import debug, do_twice
@do_twice
@debug
def greet(name):
print(f"Hello {name}")
执行结果如下:
greet("Eva")
# ---------------
Calling greet('Eva')
Hello Eva
'greet' returned None
Calling greet('Eva')
Hello Eva
'greet' returned None
4. 类装饰器
要对类进行装饰,通常有两种方法:
- 装饰类中的某个方法
- 装饰整个类
一些常见的装饰器如 classmethod
、staticmethod
、property
。这里给一个例子:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value >= 0:
self._radius = value
else:
raise ValueError("Raisus must be positive")
@property
def area(self):
return self.pi() * self.radius ** 2
def cylinder_volume(self, height):
"""Calculate volume of cylinder with circle as base"""
return self.area * height
@classmethod
def unit_circle(cls):
"""Factory method creating a circle with radius 1"""
return cls(1)
@staticmethod
def pi():
return 3.1415926
c = Circle(5)
c.radius # 5
c.area # 78.5398163375
c.radius = 2
c.cylinder_volume(height=4) # 50.2654816
Circle.unit_circle().raius # 1
装饰某个方法
import functools
import time
def timer(func):
"""
Print the runtime of the decorated function
"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter() # 1
value = func(*args, **kwargs)
end_time = time.perf_counter() # 2
run_time = end_time - start_time # 3
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return value
return wrapper_timer
class TimeWaster:
def __init__(self, max_num):
self.max_num = max_num
@timer
def waste_time(self, num_times):
for _ in range(num_times):
sum([i**2 for i in range(self.max_num)])
tw = TimeWaster(100)
tw.waste_time(999)
# Finished 'waste_time' in 0.0268 secs
装饰整个类
另一种方法是对整个类进行装饰,例如使用Python3.7
中的dataclasses模块对类进行装饰。
from dataclasses import dataclass
@dataclass
class PlayingCard:
rank: str
suit: str
对类进行装饰和对函数进行装饰比较类似,只是装饰器接收的是一个类,而不是函数。
需要注意的是,对整个类进行装饰,仅在实例化这个类的时候进行装饰,而不装饰其他方法。
import functools
import time
def timer(func):
"""
Print the runtime of the decorated function
"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter() # 1
value = func(*args, **kwargs)
end_time = time.perf_counter() # 2
run_time = end_time - start_time # 3
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return value
return wrapper_timer
@timer
class TimeWaster:
def __init__(self, max_num):
self.max_num = max_num
def waste_time(self, num_times):
for _ in range(num_times):
sum([i**2 for i in range(self.max_num)])
tw = TimeWaster(100)
# ------------------
Finished 'TimeWaster' in 0.0000 secs
tw.waste_time(999)
# No timing consuming display