YOLOv6-4.0部分代码阅读笔记-config.py
config.py
yolov6\utils\config.py
目录
config.py
1.所需的库和模块
2.class ConfigDict(Dict):
3.class Config(object):
1.所需的库和模块
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# The code is based on
# https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/config.py
# Copyright (c) OpenMMLab.
import os.path as osp
import shutil
import sys
import tempfile
from importlib import import_module
# 从 addict 库中导入 Dict 类的语句。 addict 是一个 Python 库,它提供了一个类似于字典的类 Dict ,但它增加了一些额外的功能,使得字典的使用更加灵活和方便。
from addict import Dict
2.class ConfigDict(Dict):
# 这个 ConfigDict 类的目的是提供一个字典,当尝试访问不存在的键或属性时,会抛出异常而不是返回 None 或其他默认值。这在配置管理中很有用,因为它可以确保所有必要的配置项都存在,从而避免在程序运行时出现未定义配置项的问题。
# 定义了一个名为 ConfigDict 的类,它继承自 Dict 类(一个类似于字典的类 Dict )。
class ConfigDict(Dict):
# 定义了一个特殊方法 __missing__ ,这个方法会在尝试获取字典中不存在的键时被调用。
def __missing__(self, name):
# 当尝试获取的键不存在时, __missing__ 方法会抛出一个 KeyError 异常,并传入不存在的键名。
raise KeyError(name)
# 定义了一个特殊方法 __getattr__ ,这个方法会在尝试访问对象不存在的属性时被调用。
def __getattr__(self, name):
# try 块,用于捕获在获取属性时可能发生的异常。
try:
# 尝试通过父类(即 Dict )的 __getattr__ 方法获取属性值。
value = super(ConfigDict, self).__getattr__(name)
# 如果在父类中没有找到对应的键,会捕获 KeyError 异常。
except KeyError:
# 如果捕获到 KeyError ,会创建一个 AttributeError 异常,提示用户尝试访问的属性不存在。
# {}' 对象没有属性 '{}'
ex = AttributeError("'{}' object has no attribute '{}'".format(
self.__class__.__name__, name))
# 捕获除了 KeyError 之外的其他所有异常,并将异常对象赋值给 ex 。
except Exception as e:
ex = e
# 如果没有异常发生,即属性存在,返回属性值。
else:
return value
# 如果捕获到异常,抛出异常对象 ex 。
raise ex
3.class Config(object):
# class Config(object): 定义了一个配置类,这个类用于存储和管理模型的各种配置参数。
class Config(object):
# 这段代码定义了一个静态方法 _file2dict ,它的作用是将一个 Python 文件( .py 文件)转换成一个字典,同时生成该文件内容的文本。这个方法主要用于将配置文件转换为易于处理的格式。
# 装饰器表示这个方法是一个静态方法,它不会接收类或实例的隐式第一个参数(即 self 或 cls )。
@staticmethod
# 接受一个参数 filename ,即要转换的文件名。
def _file2dict(filename):
# 确保 filename 参数是一个字符串。
filename = str(filename)
# 检查文件名是否以 .py 结尾,即是否为 Python 文件。
if filename.endswith('.py'):
# tempfile.TemporaryDirectory()
# tempfile.TemporaryDirectory() 是 Python 标准库 tempfile 模块中的一个函数,它用于创建一个临时目录。这个临时目录在创建时是空的,并且在使用完毕后可以自动删除。
# 1. 创建临时目录 : TemporaryDirectory() 函数会创建一个新的临时目录。这个目录是随机命名的,以确保不同进程或线程创建的临时目录不会冲突。
# 2. 上下文管理器 : TemporaryDirectory() 函数返回一个上下文管理器对象。这意味着你可以用 with 语句来使用它,确保临时目录在使用后能够被正确清理。
# 3. 自动清理 :当 with 语句块执行完毕时, TemporaryDirectory 对象的 __exit__ 方法会被调用,这会删除临时目录及其包含的所有文件和子目录。
# 4. 参数 : TemporaryDirectory() 函数接受一些参数,允许你自定义临时目录的创建行为,例如指定父目录、设置目录的权限等。
# 创建一个临时目录,用于存放临时文件。
with tempfile.TemporaryDirectory() as temp_config_dir:
# shutil.copyfile(src, dst)
# shutil.copyfile() 是 Python 标准库 shutil 模块中的一个函数,用于将一个文件的内容复制到另一个文件。这个函数会打开源文件和目标文件,读取源文件的内容,并将其写入目标文件。
# 参数 :
# src : 源文件的路径。
# dst : 目标文件的路径。如果目标文件已存在,则会被覆盖。
# 返回值 :
# 无返回值。
# 异常 :
# 如果源文件不存在或无法读取,或者目标文件无法写入,可能会抛出 FileNotFoundError 或 PermissionError 。
# 将原始文件复制到临时目录,并重命名为 _tempconfig.py 。
shutil.copyfile(filename,
osp.join(temp_config_dir, '_tempconfig.py'))
# sys.path.insert(index, path)
# 1.index :是要插入路径的索引位置。
# 2.path :是要插入的路径字符串。
# sys.path.insert 是 Python 标准库 sys 模块中的一个函数,它用于在 sys.path 列表中插入一个路径。 sys.path 是一个字符串列表,包含了解释器在搜索模块时会查找的目录。当你尝试导入一个模块时,Python 解释器会按照 sys.path 中的顺序来查找模块。
# 插入位置 : index 参数指定了 path 被插入到 sys.path 列表中的位置。索引从 0 开始,表示列表的第一个元素。如果 index 大于当前列表的长度, path 将被追加到列表的末尾。
# 修改 sys.path : sys.path.insert 会直接修改全局的 sys.path 列表,这意味着这个改变会影响到当前 Python 进程中所有后续的模块导入操作。
# 使用 sys.path.insert 时需要谨慎,因为不当的修改可能会导致模块导入错误或者覆盖已有的模块路径。通常,这种修改应该在程序的初始化阶段进行,以避免在程序运行过程中意外地影响到模块的导入。
# 将临时目录添加到系统路径的开头(索引 0),以便能够导入临时文件作为模块。
sys.path.insert(0, temp_config_dir)
# def import_module(name, package=None): -> 导入一个模块。
# -> 使用 _bootstrap 模块的 _gcd_import 函数来执行实际的导入操作。 name[level:] 表示去掉模块名前的点, package 是导入的上下文包, level 是相对导入的层级。
# -> return _bootstrap._gcd_import(name[level:], package, level)
# 使用前面 import_module 函数导入临时文件作为模块。
mod = import_module('_tempconfig')
# 从系统路径中移除临时目录,避免对其他模块的导入造成影响。
sys.path.pop(0)
# 创建一个字典,包含模块字典中的所有非特殊属性(即不以双下划线开头的属性)。
cfg_dict = {
name: value
for name, value in mod.__dict__.items()
if not name.startswith('__')
}
# delete imported module
# 从 sys.modules 中删除临时模块,以释放资源。
del sys.modules['_tempconfig']
else:
# 如果文件不是 .py 文件,抛出 IOError 异常,提示只支持 .py 类型的文件。
# 目前仅支持.py 类型!
raise IOError('Only .py type are supported now!')
# 初始化 cfg_text 变量,包含文件名和一个换行符。
cfg_text = filename + '\n'
# 打开原始文件进行读取。
with open(filename, 'r') as f:
# 读取文件内容,并将其追加到 cfg_text 变量中。
cfg_text += f.read()
# 返回一个元组,包含配置字典 cfg_dict 和文件内容文本 cfg_text 。
return cfg_dict, cfg_text
# 这段代码定义了 Config 类的一个静态方法 fromfile ,它的作用是从文件中读取配置并创建一个 Config 类的实例。
@staticmethod
# 接受一个参数 filename ,即要读取配置的文件名。
def fromfile(filename):
# def _file2dict(filename): -> 将一个 Python 文件( .py 文件)转换成一个字典,同时生成该文件内容的文本。 -> return cfg_dict, cfg_text
# 调用 Config 类中的私有静态方法 _file2dict ,传入 filename 参数。这个方法的作用是将文件内容转换为一个字典 cfg_dict 和一个包含文件内容的字符串 cfg_text 。
cfg_dict, cfg_text = Config._file2dict(filename)
# 使用从文件中读取的配置字典 cfg_dict 和文件内容 cfg_text ,以及文件名 filename 来创建一个新的 Config 类实例,并返回这个实例。
return Config(cfg_dict, cfg_text=cfg_text, filename=filename)
# 1.cfg_dict : 配置字典,用于存储配置项的键值对,默认为 None 。
# 2.cfg_text : 配置文本,用于存储配置文件的原始文本内容,默认为 None 。
# 3.filename : 配置文件名,用于指定配置文件的路径,默认为 None 。
def __init__(self, cfg_dict=None, cfg_text=None, filename=None):
# 如果 cfg_dict 参数为 None ,则将其设置为一个空字典。
if cfg_dict is None:
cfg_dict = dict()
elif not isinstance(cfg_dict, dict):
# 如果 cfg_dict 不是 None 但也不是字典类型,则抛出 TypeError 异常。
# cfg_dict 必须是一个字典,但得到的是 {}。
raise TypeError('cfg_dict must be a dict, but got {}'.format(
type(cfg_dict)))
# __setattr__(self, name, value)
# 1.self : 类的实例。
# 2.name : 要设置的属性的名称。
# 3.value : 要设置的属性的值。
# 在 Python 中, __setattr__ 是一个特殊方法,也称为魔术方法或内置方法。当设置对象的属性时,会自动调用这个方法。如果一个类中定义了 __setattr__ 方法,那么在设置任何属性时都会触发它,而不是直接设置属性。
# 使用 super 函数调用父类的 __setattribute__ 方法,设置实例的 _cfg_dict 属性。这里使用 ConfigDict 类来包装 cfg_dict ,以便提供额外的功能(如属性访问和类型检查)。
# ConfigDict(cfg_dict) 创建一个 ConfigDict 实例,传入 cfg_dict 作为参数。
super(Config, self).__setattr__('_cfg_dict', ConfigDict(cfg_dict))
# 使用 super 函数设置实例的 _filename 属性,存储配置文件名。
super(Config, self).__setattr__('_filename', filename)
# 如果 cfg_text 参数不为 None ,则直接使用它作为配置文本。
if cfg_text:
text = cfg_text
# 如果 cfg_text 为 None 但 filename 不为 None ,则从文件中读取配置文本。
elif filename:
with open(filename, 'r') as f:
# 读取文件内容并赋值给 text 变量。
text = f.read()
# 如果 cfg_text 和 filename 都为 None ,则设置空字符串作为配置文本。
else:
text = ''
# 使用 super 函数设置实例的 _text 属性,存储配置文本。
super(Config, self).__setattr__('_text', text)
# @property
# 在 Python 中, @property 装饰器用于将一个方法转换为属性访问器,使得可以通过属性访问的方式来调用这个方法。这种方式提供了一种更自然和直观的方式来访问和修改对象的状态。
# 这段代码定义了一个名为 filename 的属性访问器。当你使用 obj.filename 访问这个属性时,Python 解释器会自动调用这个方法,并返回 self._filename 的值。
# 这个属性访问器的作用是 :
# 1. 封装性 :隐藏了 _filename 属性的实现细节,使得外部代码只能通过 filename 属性访问器来获取文件名。
# 2. 只读属性 :由于没有提供对应的 setter 方法, filename 属性是只读的。外部代码不能直接修改 _filename 属性的值。
# 3. 延迟计算 :可以在 filename 方法中添加额外的逻辑,例如延迟计算文件名,而不是直接存储文件名。
# 总之, @property 装饰器提供了一种方便的方式来控制属性的访问,使得代码更加简洁和易于维护。
@property
def filename(self):
return self._filename
@property
def text(self):
# 表示当访问 text 属性时,这个方法将返回实例的私有属性 _text 的值。
return self._text
# 这个 __repr__ 方法的作用是提供一个清晰、准确的对象表示,使得当打印 Config 实例或在调试时查看实例时,可以获得关于实例的有用信息。这个方法的返回值通常包括对象的类名、关键属性和重要的内部状态。
# 这段代码定义了 Config 类的 __repr__ 方法,这是一个特殊方法,用于返回对象的官方字符串表示形式,通常用于调试和开发。
def __repr__(self):
# 这行代码构建并返回一个字符串,该字符串描述了 Config 实例的官方字符串表示形式。
# self.filename 是 Config 实例的 filename 属性,它返回配置文件的路径。
# self._cfg_dict.__repr__() 是对 _cfg_dict 属性调用 __repr__ 方法,
# _cfg_dict 是一个 ConfigDict 实例,它包含了配置项的键值对。
# __repr__ 方法返回 _cfg_dict 的官方字符串表示形式,通常是一个可打印的字典表示。
return 'Config (path: {}): {}'.format(self.filename,
self._cfg_dict.__repr__())
# 例:
# 创建 Config 类的实例
# config = Config('path/to/config.py', {'key': 'value'})
# 打印 Config 实例
# print(repr(config)) # 输出: Config (path: path/to/config.py): {'key': 'value'}
# 这段代码定义了 Config 类的 __getattr__ 方法,这是一个特殊方法,用于处理访问未定义属性的情况。
# 1.self : 类的实例。
# 2.name : 尝试访问的属性名称。
def __getattr__(self, name):
# 当尝试访问 Config 实例的属性时,如果该属性在 Config 类中未定义,Python 解释器会自动调用 __getattr__ 方法。这个方法会尝试从 self._cfg_dict (一个 ConfigDict 实例)中获取名为 name 的属性。
# getattr(object, name[, default])
# getattr() 是一个内置函数,用于获取对象的属性值。
# object :必需参数,表示要从中获取属性的对象。
# name :必需参数,表示要获取的属性的名称,应该是一个字符串。
# default :可选参数,表示当属性不存在时返回的默认值。如果未提供此参数并且属性不存在, etattr() 函数将引发 AttributeError 异常。
# 这个 __getattr__ 方法的作用是允许 Config 类的实例通过属性访问的方式动态地访问 _cfg_dict 中的配置项。这意味着你可以使用点符号( . )来访问配置字典中的值,而不是使用字典的键访问语法( [] )。
return getattr(self._cfg_dict, name)
# 例:
# 创建 Config 类的实例
# config = Config({'key1': 'value1', 'key2': 'value2'})
# 访问配置项
# print(config.key1) # 输出: value1
# print(config.key2) # 输出: value2
# 这段代码定义了 Config 类的 __setattr__ 方法,这是一个特殊方法,用于在设置对象属性时被自动调用。
# 1.self : 类的实例。
# 2.name : 要设置的属性的名称。
# 3.value : 要设置的属性的值。
def __setattr__(self, name, value):
# 检查 value 是否是一个字典类型。
if isinstance(value, dict):
# 如果 value 是一个字典,那么将其转换为 ConfigDict 类的实例。 ConfigDict 是一个扩展自 dict 的类,提供了额外的功能,比如属性访问和类型检查。
value = ConfigDict(value)
# 使用 self._cfg_dict (一个 ConfigDict 实例)的 __setattr__ 方法来设置属性。这里使用 __setattr__ 而不是直接赋值,是为了确保 self._cfg_dict 中的属性设置能够保持 ConfigDict 的行为,比如类型检查和特殊方法调用。
# 这个方法的作用是允许 Config 类的实例动态地设置 _cfg_dict 中的配置项,并且如果设置的值是字典类型,会自动将其转换为 ConfigDict 实例,从而保持配置项的一致性和 ConfigDict 提供的额外功能。
self._cfg_dict.__setattr__(name, value)
# 例:
# 创建 Config 类的实例
# config = Config()
# 动态设置配置项
# config.key1 = 'value1' # 设置一个字符串值
# config.key2 = {'subkey1': 'subvalue1'} # 设置一个字典值,将被转换为 ConfigDict
# 访问配置项
# print(config.key1) # 输出: value1
# print(config.key2) # 输出: ConfigDict({'subkey1': 'subvalue1'})