装饰器是一个基础概念也是让初学者很迷糊的概念,但每个中大型工程里面都会用到,搞不清楚的话就会看得云里雾里,很多功能找不到在哪里实现的。比如以下这个工程实例:(出自Featdepth模型源码)
from mono.model.registry import MONOfrom mmcv import Configdef main(): args = parse_args() cfg = Config.fromfile(args.config) model_name = cfg.model['name'] # 返回模型名称字符串 model = MONO.module_dict[model_name](cfg.model) ……if __name__ == '__main__': main()看到这里就会疑惑 model = MONO.module_dictmodelname(cfg.model)这一句中module_dict是哪里来的,来看看导入的MONO模块:
class Registry(object): def __init__(self, name): self._name = name self._module_dict = dict() @property def name(self): return self._name @property def module_dict(self): return self._module_dict def _register_module(self, module_class): """Register a module. Args: module (:obj:`nn.Module`): Module to be registered. """ if not issubclass(module_class, nn.Module): raise TypeError( 'module must be a child of nn.Module, but got {}'.format( module_class)) module_name = module_class.__name__ if module_name in self._module_dict: raise KeyError('{} is already registered in {}'.format( module_name, self.name)) self._module_dict[module_name] = module_class def register_module(self, cls): self._register_module(cls) return clsMONO = Registry('mono')原来MONO是一个实例化的Registry对象,里面有module_dict这个属性,但是这个字典里面目前是空的,如何取到里面的元素呢?
可以发现mono.model有个_init_.py文件,这个文件在模块导入时候会自动执行,里面内容是:
from .mono_baseline.net import Baselinefrom .mono_autoencoder.net import autoencoderfrom .mono_fm.net import mono_fmfrom .mono_fm_joint.net import mono_fm_joint说明这些.net文件在导入的时候也被执行了。随便打开其中一个.net文件:
rom ..registry import MONO@MONO.register_moduleclass autoencoder(nn.Module): def __init__(self, options): super(autoencoder, self).__init__() self.opt = options ……这里的@MONO.register_module就是用到了装饰器,这个函数在Registry类中定义,功能就是将被装饰的类以类名为key,存入module_dict中,所以在本文最开始的代码中:
model = MONO.module_dict[model_name](cfg.model)这一句就可以用模型名称(model_name)从module_dict中取到,同时进行实例化了。
可以说这是装饰器的一个很典型的应用,可以实现设计模式中的工厂模式,很方便地将各个模块在定义时即注册到工厂中,从而可以很灵活地使用。通过这个例子深入研究装饰器,结合网上的各种教程,可以总结出以下关键点:
装饰器是闭包的语法糖
所谓语法糖及语法上完全等价的表达方式。装饰器和闭包是完全等价的,及本身是一个函数,接收的参数是函数,返回的还是一个函数:
def funcA(func): def funcB(): print('funcA') func() return funcB此时如果用funcA来装饰一个函数:
@funcAdef funcC(): print('funcC')等价于闭包的写法:
funcC = funcA(funcC)可见装饰器和闭包的功能都是对函数功能进行增强,变成一个新的增强函数。
装饰器在定义时及执行,在被装饰函数被调用时不再执行
在funcA中加一句打印:
def funcA(func): print('funcAAA') def funcB(): print('funcA') func() return funcB@funcAdef funcC(): print('funcC')多个装饰器的执行顺序:定义时从下往上,调用时从上往下
def func_a(func): print(1) def func_a1(): print(11) func() return func_a1def func_b(func): print(2) def func_b1(): print(22) func() return func_b1@func_a@func_bdef func_c(): print(3)这个其实也好理解,写成闭包的形式:func_c = func_a(func_b(func_c)) 因为内层优先级高,所以定义时从内向外执行,而在调用时,func_c已经变成了func_a的内层函数funca1,里面又包含了func_b的内层函数func_b1,所以从外向内执行,相当于入栈和出栈。
带参数和返回值的装饰函数写法
被装饰函数有参数和返回值:
def func_a(func): def func_a1(*args, **kwargs): a = func(*args, **kwargs) + 1 return a return func_a1@func_adef func_b(num): return num调用:
m = func_b(100)print(m)可见如果被装饰函数有参数和返回值,则在装饰器的内层函数中设置相应的参数和返回值,和被装饰函数保持一致即可。参数可写成通用模式:*args, **kwargs
装饰器有参数和返回值
def func_a(num): def func_a1(func): def func_a2(*args, **kwargs): a = func(*args, **kwargs) + num return a return func_a2 return func_a1@func_a(100)def func_b(a): return a调用:
m = func_b(1000)print(m)装饰器或被装饰对象是类的情况
被装饰对象是类:
def wrapClass(cls): def inner(para): print('param name:', para) return cls(para) return inner@wrapClassclass Foo(): def __init__(self, a): self.a = a def fun(self): print('self.a =', self.a)调用:
Foo('apple')可见如果被装饰对象是类,那装饰器返回的也应该是类
装饰器是类:
先看类装饰类的情况:
class ShowClassName(object): def __init__(self, cls): self._cls = cls def __call__(self, a): print('class name:', self._cls.__name__) return self._cls(a)@ShowClassNameclass Foobar(object): def __init__(self, a): self.value = a def fun(self): print(self.value)调用:
a = Foobar('apple')再看类装饰函数的情况:
class ShowFunName(): def __init__(self, func): self._func = func def __call__(self, a): print('function name:', self._func.__name__) return self._func(a)@ShowFunNamedef Bar(a): return a调用:
print(Bar('apple'))可见如果装饰器是类,在调用被装饰对象的时候会调用装饰器的__call__方法,相当于装饰器是函数情况的内层函数,如果被装饰对象是类则返回类,是函数则返回函数。总之无论装饰器还是被装饰对象是类,本质上和函数装饰函数是一样的,都是返回对被装饰对象的增强。
| 留言与评论(共有 0 条评论) “” |