1. 抽象基类
抽象基类(ABC abstract base class):只定义(通用)接口,不涉及实现。在C++中,抽象基类必须至少包含一个纯虚函数,而当类中包含纯虚函数时,则不能创建该类的对象,只能作为基类。
从Python2.6
开始,标准库提供了抽象基类。大多数抽象基类在collections.abc
模块中定义。
1.1 定义并使用一个抽线基类
以fileio
中,涉及文件读写的FileHandler
为例,FileHandler
的作用是对外提供统一的文件读写API,根据待读写文件的后缀名选择对象的类handler
进行具体操作。
抽象基类命名为BaseFileHandler
,包含5
个方法,其中三个为抽象方法:
load_from_fileobj
:读取文件内容dump_to_fileobj
:将内容写入到文件中dump_to_str
:将内容以字符串的形式返回
另外两个具体方法:
load_from_path
:根据文件名获取文件句柄,传入load_from_fileobj
中dump_to_path
:根据需要写入文件名获取文件句柄,传入dump_to_fileobj
BaseFileHandler
抽线基类的定义如下所示。
# 继承collection.abc中的ABCMeta,声明BaseFileHandler为抽线基类
class BaseFileHandler(metaclass=ABCMeta): # Python 3.4之前抽象基类的定义
# 抽象方法:在子类中具体实现
@abstractmethod
def load_from_fileobj(self, file, **kwargs): # <2>
''' 读取文件中的数据内容 '''
pass
@abstractmethod
def dump_to_fileobj(self, obj, file, **kwargs):
''' 将数据内容写入到文件中 '''
pass
@abstractmethod
def dump_to_str(self, obj, **kwargs):
''' 将数据内容转换为字符串类型 '''
pass
def load_from_path(self, filepath, mode='r', **kwargs):
''' 获取需要读入数据的文件句柄 '''
with open(filepath, mode) as f:
return self.load_from_fileobj(f, **kwargs)
def dump_to_path(self, obj, filepath, mode='w', **kwargs):
''' 获取需要写入数据的文件句柄 '''
with open(filepath, mode) as f:
self.dump_to_fileobj(obj, f, **kwargs)he
1.2 定义抽象基类BaseFileHandler
的子类
在MMCV
中,开发了三个具体的子类。
JsonHandler
YamlHandler
PickleHandler
以常见的json
文件读写类JsonHandler
为例,通过调用json
库的函数完成文件读写
class JsonHandler(BaseFileHandler):
def load_from_fileobj(self, file):
return json.load(file) # 将json字符串解码成Python对象
def dump_to_fileobj(self, obj, file, **kwargs):
kwargs.setdefault('default', set_default)
json.dump(obj, file, **kwargs)
def dump_to_str(self, obj, **kwargs):
kwargs.setdefault('default', set_default)
return json.dumps(obj, **kwargs) # 将Python对象编码为Jhe
1.3 封装对外提供读写接口
file_handlers = {
'json': JsonHandler(),
'yaml': YamlHandler(),
'yml': YamlHandler(),
'pickle': PickleHandler(),
'pkl': PickleHandler()
}# MMCV已经实现的子类
def load(file, file_format=None, **kwargs):
"""Load data from json/yaml/pickle files.
This method provides a unified api for loading data from serialized files.
Args:
file (str or :obj:`Path` or file-like object): Filename or a file-like
object.
file_format (str, optional): If not specified, the file format will be
inferred from the file extension, otherwise use the specified one.
Currently supported formats include "json", "yaml/yml" and
"pickle/pkl".
Returns:
The content from the file.
"""
if isinstance(file, Path): # 判断file是否是文件路径
file = str(file)
if file_format is None and is_str(file):
file_format = file.split('.')[-1] # 根据文件后缀名推断文件类型
if file_format not in file_handlers: # 判断是否支持该文件类型的读取
raise TypeError(f'Unsupported format: {file_format}')
handler = file_handlers[file_format] # 根据文件类型得到对应的FileHandler类对象
if is_str(file):
obj = handler.load_from_path(file, **kwargs) # 根据文件名读取数据内容
elif hasattr(file, 'read'):
obj = handler.load_from_fileobj(file, **kwargs) # 文件对象
else:
raise TypeError('"file" must be a filepath str or a file-object')
return obj
def dump(obj, file=None, file_format=None, **kwargs):
"""Dump data to json/yaml/pickle strings or files.
This method provides a unified api for dumping data as strings or to files,
and also supports custom arguments for each file format.
Args:
obj (any): The python object to be dumped.
file (str or :obj:`Path` or file-like object, optional): If not
specified, then the object is dump to a str, otherwise to a file
specified by the filename or file-like object.
file_format (str, optional): Same as :func:`load`.
Returns:
bool: True for success, False otherwise.
"""
if isinstance(file, Path):
file = str(file)
if file_format is None:
if is_str(file):
file_format = file.split('.')[-1]
elif file is None:
raise ValueError(
'file_format must be specified since file is None')
if file_format not in file_handlers:
raise TypeError(f'Unsupported format: {file_format}')
handler = file_handlers[file_format]
if file is None:
return handler.dump_to_str(obj, **kwargs)
elif is_str(file):
handler.dump_to_path(obj, file, **kwargs)
elif hasattr(file, 'write'):
handler.dump_to_fileobj(obj, file, **kwargs)
else:
raise TypeError('"file" must be a filename str or a file-object')
通过上述封装,用户只需要读写文件时,调用load
和dump
两个函数就行
2. 函数装饰器和闭包
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。
2.1 基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。
#! 装饰器将函数替换成另一个函数
def deco(func):
def inner():
print("runing inner()")
return inner # <1>
@deco
def target(): # <2>
print("runing target()")
# 调用
target() # <3>
# 输出 running inner()
- <1>:在
Python
中函数是“一等公民”可以像变量一样传递,deco
返回inner
函数对象 - <2>:使用
deco
装饰target
- <3>:调用被装饰的
target
其实会执行inner
装饰器的执行时机:装饰器在加载模块时立即执行
2.2 闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。
# 计算移动平均值的高阶函数
>>> def make_average():
... series = list()
...
... def average(new_value):
... series.append(new_value)
... total = sum(series)
... return total / len(series)
... return average
...
>>> avg = make_average()
>>> avg(10)
10.0
>>> avg(11)
10.5
🔨series
是make_average
函数的局部变量,当调用avg(10)
时,make_average
函数已经返回,即series存储的空间已经被释放。
在averager
函数中,*series
*是自由变量,指未在本地作用域绑定的变量
对于数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,则会隐式创建局部变量,使得其不再是自由变量,就不会保存在闭包中
在Python3
中,引入了nonlocal
声明,把变量标记为自由变量
2.3 参数化装饰器
Python中将被装饰的函数作为第一个参数传给装饰器函数,可以通过创建一个装饰器工厂函数,将其他参数传给装饰器,其返回值为装饰器函数。
通过装饰器工厂函数,应用闭包技术,将参数传递给装饰器函数
2.4 参数化装饰器的应用-自定义拓展
还是以上面文件读写FileHandler为例,假设我们现在读取的文件格式MMCV不支持,我们可以自定义扩展开发。
def _register_handler(handler, file_formats):
"""Register a handler for some file extensions.
Args:
handler (:obj:`BaseFileHandler`): Handler to be registered.
file_formats (str or list[str]): File formats to be handled by this
handler.
"""
if not isinstance(handler, BaseFileHandler):
raise TypeError(
f'handler must be a child of BaseFileHandler, not {type(handler)}')
if isinstance(file_formats, str):
file_formats = [file_formats]
if not is_list_of(file_formats, str):
raise TypeError('file_formats must be a str or a list of str')
for ext in file_formats: # 将后缀名与自定义文件读写类对应
file_handlers[ext] = handler
def register_handler(file_formats, **kwargs):# 装饰器工厂函数
# file_formats 自由变量
def wrap(cls): #类的装饰器
_register_handler(cls(**kwargs), file_formats)
return cls
return wrap
MMCV
通过装饰器工厂函数,将文件后缀名与自定义实现的类对应,并添加到file_handlers
中。
以npy文件读写为例,具体实现如下
@register_handler('npy')
class NpyHandler(BaseFileHandler):
def load_from_fileobj(self, file, **kwargs):
return np.load(file)
def dump_to_fileobj(self, obj, file, **kwargs):
np.save(file, obj)
def dump_to_str(self, obj, **kwargs):
# 实际上这么写没有意义,这里只是举例
return obj.tobytes()
通过register_handler
将后缀名npy
和对应的类NpyHandler
添加到file_handler
中,此时访问该对象得到的内容应该是
file_handlers = {
'json': JsonHandler(),
'yaml': YamlHandler(),
'yml': YamlHandler(),
'pickle': PickleHandler(),
'pkl': PickleHandler(),
'npy': NpyHandler()
}
总结
抽象基类和装饰器在MMCV
中被广泛使用,了解这两个基础知识有利于对MMCV
框架的理解学习