Python @函数装饰器及用法
1. 问题1
在阅读detectron2代码时,经常能看到@符号,例如在init函数上搞了一个@configurable,实例化RetinaNet类时没有先执行init内的代码,而是直接进入了config.py的configurable函数,让人很是不解。
class RetinaNet(nn.Module):
"""
Implement RetinaNet in :paper:`RetinaNet`.
"""
@configurable
def __init__(
self,
*,
backbone: Backbone,
head: nn.Module,
head_in_features,
anchor_generator,
box2box_transform,
anchor_matcher,
num_classes,
focal_loss_alpha=0.25,
focal_loss_gamma=2.0,
smooth_l1_beta=0.0,
box_reg_loss_type="smooth_l1",
test_score_thresh=0.05,
test_topk_candidates=1000,
test_nms_thresh=0.5,
max_detections_per_image=100,
pixel_mean,
pixel_std,
vis_period=0,
input_format="BGR",
):
2. 函数装饰器
那么 @configurable 到低啥意思,有啥作用?这其实是函数装饰器🤣。
假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示:
#funA 作为装饰器函数
def funA(fn):
print("A_1")
fn("B") # 执行传入的fn参数
print("A_2")
return "Result"
@funA
def funB(str):
print(str)
print("-----")
print(funB)
使用函数装饰器 A() 去装饰另一个函数 B(),其底层执行了如下 2 步操作:
- 将 B 作为参数传给 A() 函数
- 将 A() 函数执行完成的返回值反馈回 B
故上述代码可以等价为
def funA(fn):
print("A_1")
fn("B") # 执行传入的fn参数
print("A_2")
return "Result"
def funB(str):
print(str)
funB = funA(funB)
print("-----")
print(funB)
它们的输出均为
A_1
B
A_2
-----
Result
回到之前的问题,configurable函数去装饰init函数,所以init = configurabl(init)
3. 问题2
可是不对啊,为啥调试的时候直接跑到了configurable函数里的wrapped,这又是搞哪出?
def configurable(init_func=None, *, from_config=None):
if init_func is not None:
assert (
inspect.isfunction(init_func)
and from_config is None
and init_func.__name__ == "__init__"
), "Incorrect use of @configurable. Check API documentation for examples."
@functools.wraps(init_func)
def wrapped(self, *args, **kwargs):
try: #直接跑这来了!
from_config_func = type(self).from_config
except AttributeError as e:
raise AttributeError(
"Class with @configurable must have a 'from_config' classmethod."
) from e
if not inspect.ismethod(from_config_func):
raise TypeError("Class with @configurable must have a 'from_config' classmethod.")
if _called_with_cfg(*args, **kwargs):
explicit_args = _get_args_from_config(from_config_func, *args, **kwargs)
init_func(self, **explicit_args)
else:
init_func(self, *args, **kwargs)
return wrapped
else:
if from_config is None:
return configurable
assert inspect.isfunction(
from_config
), "from_config argument of configurable must be a function!"
def wrapper(orig_func):
@functools.wraps(orig_func)
def wrapped(*args, **kwargs):
if _called_with_cfg(*args, **kwargs):
explicit_args = _get_args_from_config(from_config, *args, **kwargs)
return orig_func(**explicit_args)
else:
return orig_func(*args, **kwargs)
return wrapped
return wrapper
4. 理解@functools.wraps()
这就涉及到另一个点了,Python装饰器(decorator)在实现的时候,被装饰后的函数其属性会发生改变
例如之前的funA、funB例子,funA在装饰funB之后,funB相当于一字符串
那么为了保证被装饰器装饰后的函数仍拥有原来的属性,Python的functools包中提供了一个叫wraps的装饰器(decorator)来消除这样的副作用。
对之前funA和funB示例进行小改,得到如下代码:
#funA 作为装饰器函数
import functools
def funA(func=None):
print("A_1")
if func is not None:
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('Calling decorated function...')
func(*args, **kwargs)
return wrapper
print("A_2")
return "Result"
@funA
def funB(str):
print(str)
print("--分界线,开始执行funB---")
funB("hhhhh")
执行结果如下:
A_1
--分界线,开始执行funB---
Calling decorated function...
hhhhh
可见被装饰的funB仍旧保持函数性质可被执行,只不过此时的funB和原先不同,此时funB等价于
def funB(str):
print('Calling decorated function...')
print(str)
5. 总结
问题1中谈到实例化RetinaNet类时直接进入configurable函数的说法貌似不太准确,装饰器通常是在导入时(即 Python 加载模块时)便发挥作用,之后被装饰的函数其属性将发生变化。
问题2提到调试时直接进入configurable函数里的wrapped函数,这是因为其使用了wraps装饰器(decorator),所以init函数仍旧拥有原来的属性,只不过执行的内容由于装饰器的缘故而做了一定的补充。
参考链接
https://blog.csdn.net/liuzonghao88/article/details/103586634
https://www.cnblogs.com/hosseini/p/7814941.html