装饰器
文章目录
作用: 在代码运行期间动态增加功能的方式。本质上,decorator就是一个返回函数的高阶函数。
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数
@log
def now():
print('2015-3-25')
# 上述函数等价于下列语句
now = log(now)
由于
log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
0. 一些小知识点
0.1 类方法与实例方法
类方法:用static修饰的方法。
由于类方法是属于整个类的,所以类方法的方法体中不能有与类的对象有关的内容。
即类方法体有如下限制:
- 类方法中不能引用对象变量;
- 类方法中不能调用类的对象方法;
- 在类方法中不能调使用super,this关键字;
- 类方法不能被覆盖。
实例方法:当一个类创建了一个对象后,这个对象就可以调用该类的方法(对象方法)。
- 实例方法中可以引用对象变量,也可以引用类变量;
- 实例方法中可以调用类方法;
- 对象方法中可以使用super,this关键字。
区别:
类方法可以通过类名调用,实例方法不能通过类名调用
当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址
当该类创建对象后,类中的实例方法才分配入口地址,
从而实例方法可以被类创建的任何对象调用执行。
类方法在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出时才被取消。
注意:
当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址。也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
在Java语言中,类中的类方法不可以操作实例变量,也不可以调用实例方法,这是因为在类创建对象之前,实例成员变量还没有分配内存,而且实例方法也没有入口地址
注:类方法的使用在9.4中讲解
注2:静态方法在此处不解释
0.2 抽象类、抽象方法、抽象属性
作用:本质上,抽象类是要求编程人员在实例化对象时须遵循某些协议(要求类有某些能力),而出现的一种机制。实现抽象类须重写抽象类全部的抽象方法与抽象属性,若未全部重写,则继承的子类仍为抽象类
抽象类分类:
- 真实子类
- 虚拟子类
0.2.1 真实子类
真实子类:真实子类就是子类直接从抽象基类(抽象基类提供基本类和最基本的抽象方法,可以为子类定义共有的方法,但不需要具体实现)派生,抽象基类中可以定义”抽象方法“和“抽象属性”, 抽象基类可以不实现具体的方法,也可以实现部分,子类继承抽象基类的抽象内容并实现,只有完全重写了抽象基类中的“抽象”内容后,才能被实例化,如果有个抽象内容没有重写则子类本身也是抽象类,不能实例化
抽象基类的实现:
from abc import ABC, abstractmethod, abstractproperty
'''
导入abc模块的抽象基类ABC、抽象方法abstractmethod、抽象属性abstrctproperty
'''
class A(ABC):
'''
定义抽象基类:抽象基类要求从ABC类或其子类派生
'''
__metaclass__ = abc.ABCMeta
@abstractmethod # 抽象方法:子类必须实现
def load(self, input):
return
@abstractmethod
def save(self, output, data):
return
@abstractproperty # 抽象属性:子类必须实现
def value(self):
return 'should never get here.'
class B(A):
def load(self, input):
return input.read()
def save(self, output, data):
return output.write(data)
@property # 方法属性话
def value(self):
return 'okk'
>>> b = B()
>>> b.value
okk
0.2.2 虚拟子类
虚拟子类:虚拟子类是将其他的不是从抽象基类派生的类”注册“到抽象基类,让
Python
解释器将该类作为抽象基类的子类使用,因此称为虚拟子类。这样第三方类不需要直接继承自抽象基类。注册的虚拟子类不论是否实现抽象基类中的抽象内容,Python
都认为它是抽象基类的子类,调用issubclass(子类,抽象基类)
,isinstance (子类对象,抽象基类)
都会返回True
。
- 这种通过注册增加虚拟子类是抽象基类动态性的体现,也是符合Python风格的方式。它允许我们动态地,清晰地改变类的属别关系。当一个类继承自抽象基类时,该类必须完成抽象基类定义的语义;当一个类注册为虚拟子类时,这种限制则不再有约束力,可以由程序开发人员自己约束自己,因此提供了更好的灵活性与扩展性
1. 方法属性化–@property
Python内置的@property
装饰器就是负责把一个方法变成属性调用的
@property
的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值
注:本质上有点像java上的set()和get()的实现,不过在现今的微服务框架下采用lombok插件可以一键实现两个方法,而python里面是将其弄成装饰器形式
class Rectangle():
def __init__(self,length,width): self.width,self.length = width,length
@property #定义getter装饰器
def len(self):
print("execute getLen")
return self.length
@len.setter #定义setter装饰器
def len(self,length):
print("execute setLen")
self.length=length
@len.deleter #定义deleter 装饰器
def len(self):self.length=0
2. 注册类–@Class.register(“module”)
作用:直观上就是将目标模块注册到字典(容器)中便于管理(register.py
结合build.py
一起使用)
在早期,我们在深度学习项目中使用字典管理的模块的方式如下:
>>> models/__init__.py
from __future__ import absolute_import
from .ResNet import *
from .ResNeXt import *
from .SEResNet import *
......
__factory = {
'resnet50': ResNet50,
'resnet101': ResNet101,
'seresnet50': SEResNet50,
.....
}
def get_names():
return __factory.keys()
def init_model(name, *args, **kwargs):
if name not in __factory.keys():
raise KeyError("Unknown model: {}".format(name))
return __factory[name](*args, **kwargs)
- 在
train.py
中调用model
时我们仅需要输入模块相对应的key
即可,此时字典返回value
。类名表示为指向类的变量(无论是变量名,函数名,亦或是类名都是指向方法的变量。即将类名赋予另一变量,则该变量指向目标类)- 上述方法在添加新模块时,需要将新的模块名添加进字典,十分繁琐,因而在大的深度学习项目中,我们使用
@class.register('key_name')
装饰器将目标模块直接添加进字典,便于管理
Register()
类的定义:
>>> register.py
def _register_generic(module_dict, module_name, module):
assert module_name not in module_dict
module_dict[module_name] = module
class Registry(dict):
'''
A helper class for managing registering modules, it extends a dictionary
and provides a register functions.
Eg. creating a registry:
some_registry = Registry({"default": default_module})
There're two ways of registering new modules:
1): normal way is just calling register function:
def foo():
...
some_registry.register("foo_module", foo)
2): used as decorator when declaring the module:
@some_registry.register("foo_module")
@some_registry.register("foo_modeul_nickname")
def foo():
...
Access of module is just like using a dictionary, eg:
f = some_registry["foo_modeul"]
'''
def __init__(self, *args, **kwargs):
super(Registry, self).__init__(*args, **kwargs)
def register(self, module_name, module=None):
'''
装饰器定义
'''
# used as function call
if module is not None:
_register_generic(self, module_name, module)
return
# used as decorator
def register_fn(fn):
_register_generic(self, module_name, fn)
return fn
return register_fn
Register()
继承自dict()
类,为字典的子类- 如上所述,
Register()
类提供了一种便于管理注册类的方式,主要以字典的方式。
使用Register()
管理模块的方式如下:
>>> losses/register.py
from register import Registry
LOSS = Registry() # 实例化Register()类,LOSS本质上是一个字典
>>> losses/loss.py
import torch
from torch import nn
from torch.nn.functional as F
from .registry import LOSS
'''
在loss.py文件中导入register.py文件中实例化的对象LOSS,该LOSS本质上为字典
'''
@LOSS.register('ccloss') # 装饰器,key为‘ccloss’,value为CCLoss
class CCLoss(nn.Module):
def __init__(self):
super(CCLoss, self).__init__()
'''
继承超类(父类)的方法
'''
pass
def forward(self, input, label):
pass
在大的深度学习项目中,build.py
常配合register.py
一起使用:
>>> build.py
from .registry import LOSS
def build_loss(cfg):
loss_name = cfg.LOSSES.NAME
assert loss_name in LOSS, \
f'loss name {loss_name} is not registered in registry :{LOSS.keys()}'
return LOSS[loss_name](cfg)
最后,我们在__init.py
文件中使用
>>> __init__.py
from .build import build_loss
3. 类方法–@classmethod
作用:该装饰器用于声明此方法为类方法,而非实例方法
class Class(object):
@classmethod
def func(cls, [,parameters]):
'''
Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。
'''
pass
def __init__(self):
pass
class ClsMethod():
objcnt1 = 0
@classmethod
def clsmeth1(cls):
cls.objcnt1+=1
cnt1=cls.getobjcnt() # 1. 类方法中通过cls访问类方法
cnt2=ClsMethod.getobjcnt() # 2. 类方法中通过类名访问类方法
@classmethod
def getobjcnt(cls):
print("in getobjcnt(cls)")
return ClsMethod.objcnt1
def __init__(self):
ClsMethod.clsmeth1() # 3. 实例方法中通过类名.方法名访问类方法
self.__class__.clsmeth1()# 4. 实例方法中通过self.__class__.方法名访问类方法
ClsMethod.clsmeth1()
cm1=ClsMethod()
cm1.getobjcnt()
cm1.__class__.getobjcnt()
类方法推荐使用类名直接调用:这是因为,类方法在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出时才被取消。
使用方式(如ClsMethod()):
-
类方法可以在所有类方法中通过
“类名.方法名”
方式调用; -
类方法可以在类方法中通过
“cls.方法名”
方式调用,cls
的来源请见上面类方法定义的说明; -
类方法可以在实例方法中通过
“类名.方法名”
方式调用; -
类方法可以在实例方法中通过
“self.方法名”
或“self.__class__.方法名”
方式调用
注意:
-
不能在类体中直接调用类方法时,这个时候类还未定义完,执行时会认为类未定义,会报类对象不可调用;
-
当实例方法和类方法重名时,在类体代码中后出现的方法会覆盖前面的方法,其实在Python中,同一名字空间的同名函数(不管参数是否数目相同)都会被后定义的覆盖,Python不支持一个函数不同变量的情况。由于类方法和实例方法都在类的空间内,因此二者不能重名。
4. 静态方法–@staticmethod
作用:静态方法也是通过类定义的一种方法,一般将不需要访问类属性但是类需要具有的一些能力可以静态方法提供。
class Foo(object):
"""类三种方法语法形式"""
def instance_method(self, *arg):
print("是类{}的实例方法,只能被实例对象调用".format(Foo))
@staticmethod
def static_method():
print("是静态方法")
@classmethod
def class_method(cls, *arg):
print("是类方法")
-
静态方法可以在所有类的方法中通过
“类名.方法名”
方式调用; -
静态方法可以在类方法中通过
“cls.方法名”
方式调用; -
静态方法可以在实例方法中通过
“类名.方法名”
方式调用; -
静态方法可以在实例方法中通过
“self.方法名”
或“self.__class__.方法名”
方式调用;
区别:
- 实例方法:实例方法是类被实例化以后方能调用的方法,表示实例本身所具备的能力,定义时首个参数为
self
,调用时类名.实例方法名
。 - 类方法:类方法用在与实例无关但与类或类的所有实例相关的访问场景,表示类本身所具备的能力,定义时首个参数为
cls
。
-
静态方法可以在实例方法中通过
“类名.方法名”
方式调用; -
静态方法可以在实例方法中通过
“self.方法名”
或“self.__class__.方法名”
方式调用;
区别:
- 实例方法:实例方法是类被实例化以后方能调用的方法,表示实例本身所具备的能力,定义时首个参数为
self
,调用时类名.实例方法名
。 - 类方法:类方法用在与实例无关但与类或类的所有实例相关的访问场景,表示类本身所具备的能力,定义时首个参数为
cls
。 - 静态方法:静态方法一般用在与实例和类的数据无关的场景,也可以说是类内的一个与类和实例数据无关的一个类空间内的函数,静态方法实现的能力通过普通函数一样可以实现,表示一种类内的公共能力,定义时可无需传递参数