代码笔记:Dark Experience for General Continual Learning a Strong, Simple Baseline
作为学习该论文的代码的笔记,方便理解与复习。
1."__file__"和三个文件路径的处理函数
- "__file__":
一个内置变量,表示当前模块的文件路径,根据文件的使用不同可能是绝对路径,也可能是相对路径,类型是字符串。"script.py"(直接运行时) "/home/user/script.py"(绝对路径运行或导入时) - 三个文件路径处理函数
os.path.dirname(path) - 获取路径中的目录部分(去掉文件名)
os.path.basename(path) - 获取路径中的文件名部分(最后一个斜杠后的内容)
os.path.abspath(path) - 将路径转换为绝对路径(基于当前工作目录)# 相对路径 path = 'main.py' os.path.dirname(path) # 输出为'' os.path.basename(path) # 输出为'main.py' os.path.abspath(path) # 输出为'/home/user/mammoth/utils/main.py'(转换成了绝对路径)# 绝对路径 path = '/home/user/mammoth/utils/main.py' os.path.dirname(path) # 输出为'/home/user/mammoth/utils' os.path.basename(path) # 输出为'main.py' os.path.abspath(path) # 输出为'/home/user/mammoth/utils/main.py'
2.sys.path
sys.path 是一个列表,它指定了 Python 解释器在导入模块时要搜索的目录集合。当使用 import module_name 时,Python 会按照 sys.path 中列出的顺序,在这些路径中查找名为 module_name 的文件或目录。
sys.path.insert(0, mammoth_path)如图所示的代码代表将 mammoth_path 这个路径插入到 sys.path 列表的最前面(索引为 0 的位置),这样之后当使用 import module_name 时,Python 会优先在当前的路径查找,方便使用自己的文件和模块。
3.#noqa: F401
flake8 是一个代码风格和语法检查工具,会针对代码质量、风格和潜在问题进行检查并报告警告和错误。
"#noqa:" 是一个特殊的注释,可以让flake8忽略这一行代码的某些特定的警告,其中"F401"是模块导入但未使用的警告,"#noqa: F401"可以让flake8忽略该行导入模块但未使用,从而不给出对应的警告。
简而言之这个注释不是给人看的是给flake8看的。
4.".env"文件
.env 文件是一个环境变量配置文件,用于存储应用程序的环境变量,其储存的格式为简单的键值对格式,类似于:
DATABASE_URL=postgresql://user:password@localhost/dbname
API_KEY=your_secret_key_here
DEBUG=True
SECRET_KEY=your_secret_key
HOST=localhost
PORT=8000使用方法为利用load_dotenv()直接加载,如下图所示:
import os
from dotenv import load_dotenv# 直接加载,默认加载当前目录下的.env文件
load_dotenv()
# 也可以用绝对或者相对路径加载
load_dotenv('C:/Users/YourName/project/.env')# 然后就可以用os.使用环境变量了
database_url = os.getenv('DATABASE_URL')
secret_key = os.getenv('SECRET_KEY')# 也可以附带个默认值
host = os.getenv('HOST','1234') # .env文件里找不到就用'1234'赋值.env 文件的核心好处就是将敏感配置与代码分离,避免密码密钥等机密信息泄露到版本库中。
5.TYPE_CHECKING
TYPE_CHECKING 是一个在 typing 模块中定义的常量,它只在类型检查时(如使用 mypy 等工具时)为 True,在运行时为 False,用于避免导入仅在类型提示中使用的模块。
听上去就很抽象,因为这个东西实际上不是写给人看的,是写给类似mypy这样静态类型检查器看的。
TYPE_CHECKING的作用之一是防止循环导入,先举个简单的循环导入的例子:
# husband.py
from wife import Wife # 导入妻子类class Husband:def __init__(self, name: str):self.name = nameself.wife: Wife | None = None # 需要Wife类型注解# Wife | None 表示可能是Wife类型也可能是None类型(没有妻子)def marry(self, wife: Wife) -> None:self.wife = wifeprint(f"{self.name} 娶了 {wife.name}")
# wife.py
from husband import Husband # 导入丈夫类 → 循环导入错误!class Wife:def __init__(self, name: str):self.name = nameself.husband: Husband | None = None # 需要Husband类型注解def marry(self, husband: Husband) -> None:self.husband = husbandprint(f"{self.name} 嫁给了 {husband.name}")运行husband.py时,导入了wife.py的妻子类,但是wife.py在编译妻子类时又需要用到husband.py的丈夫类,问题是现在husband.py还没编译完,所以运行时会报错:
ImportError: cannot import name 'Husband' from partially initialized module 'husband'这里最主要的一个问题就是husband.py中用了wife.py的妻子类作为类型注释,实际上,把代码中原本的Wife加个引号变成字符串作为类型注释就可以了,如下所示:
# husband.py
# from wife import Wife # 不需要导入妻子类了class Husband:def __init__(self, name: str):self.name = nameself.wife: 'Wife | None' = None # 需要Wife类型注解# Wife | None 表示可能是Wife类型也可能是None类型(没有妻子)def marry(self, wife: 'Wife') -> None:self.wife = wifeprint(f"{self.name} 娶了 {wife.name}")当然,wife.py中的丈夫类也要加引号变成字符串作为类型注释,这样代码就不会出问题了。
其中的原理就是:Python 的类型系统采用了一种"渐进式"或"可选类型"的设计。其核心原理是:类型注解在运行时会被 Python 解释器完全忽略,因此它们不会影响程序的执行(程序报错只可能源于实际的逻辑错误或异常)。然而,像 mypy 这样的静态类型检查器会在代码运行前,专门分析这些类型注解,如果发现参数类型与注解不匹配,检查器会向我们报告错误,但它依然无权阻止我们运行代码。
并且 mypy 能够正确解析字符串形式的类型注释,它会将这些字符串视为实际存在的类型进行验证,如果我们在字符串中引用了未定义或未导入的类,mypy 会认为这是一个类型错误并给出相应的提示。
所以为了不让mypy报错,我们不仅要用正确的字符串作为类型注释,还要给代码加上TYPE_CHECKING,以下是加了TYPE_CHECKING后的代码:
from typing import TYPE_CHECKINGif TYPE_CHECKING:from wife import Wifeclass Husband:def __init__(self, name: str):self.name = nameself.wife: "Wife | None" = Nonedef marry(self, wife: "Wife") -> None:self.wife = wifeprint(f"{self.name} 娶了 {wife.name}")
from typing import TYPE_CHECKINGif TYPE_CHECKING:from husband import Husbandclass Wife:def __init__(self, name: str):self.name = nameself.husband: "Husband | None" = Nonedef marry(self, husband: "Husband") -> None:self.husband = husbandprint(f"{self.name} 嫁给了 {husband.name}")mypy 在进行静态类型检查时,会将 TYPE_CHECKING 视为 True,因此它会处理条件块内的导入语句,确认 Wife 类已被正确导入。这样,当它看到字符串注释 'Wife' 时,就能正确识别其指向的实际类型。
而在 Python 运行时,TYPE_CHECKING 为 False,条件导入语句会被跳过,从根本上避免了循环导入问题,同时,由于类型注解使用的是字符串形式,运行时无需解析这些类型信息,也就不像自定义类一样依赖包的导入,所以也不影响代码的正确运行。
TYPE_CHECKING 的另一个重要作用是提升性能。有些第三方库导入很慢,或者会占用大量内存,如果导入这些库仅仅是为了类型注解(实际运行时并不需要),就会造成不必要的资源浪费,通过把这些导入放在 "if TYPE_CHECKING:" 块里,可以确保它们只在静态检查时被识别,而不会在程序运行时被实际加载,从而节省了启动时间和内存开销。
