整体说明
OmegaConf 是一个基于 YAML 的分层配置系统,由 Facebook AI Research (FAIR) 开发,广泛应用于 PyTorch Lightning、Hydra 等框架中
OmegaConf 支持从多种来源(文件、命令行参数、环境变量)合并配置,并提供统一的访问 API 和运行时类型安全
OmegaConf 是可通过 pip 快速安装
1
pip install omegaconf
- 注:OmegaConf 要求 Python 3.8 或更新版本
实践经验:
- YAML 是推荐格式 :OmegaConf 基于 YAML 设计,对 YAML 支持最完善
- 类型安全 :结构化配置提供运行时类型检查,建议在大型项目中使用
- 合并顺序 :
merge()时后面的配置覆盖前面的 - 插值解析 :插值默认在访问时解析,而非创建时
- 环境变量 :使用
oc.env解析器,而非直接插值
OmegaConf 创建配置
从字典创建
1
2
3
4
5
6from omegaconf import OmegaConf
conf = OmegaConf.create({
"server": {"host": "localhost", "port": 8080},
"database": {"user": "admin", "password": "secret"}
})- OmegaConf 支持
str、int、bool、float、bytes和Enum作为字典的键类型
- OmegaConf 支持
从列表创建
1
2
3
4
5
6
7
8
9conf = OmegaConf.create([1, {"a": 10, "b": {"a": 10} }])
## 创建的是一个包含两个元素的列表
# [
# 1, # 第0个元素:整数
# {"a": 10, "b": {"a": 10}} # 第1个元素:嵌套字典
# ]
## 通过索引访问
# print(conf[0]) # 输出: 1
# print(conf[1]) # 输出: {'a': 10, 'b': {'a': 10}}- 类似列表,也支持从元组同样创建
从 YAML 文件加载
1
conf = OmegaConf.load('config.yaml')
从 YAML 字符串创建
1
2
3
4
5
6s = """
server:
host: localhost
port: 8080
"""
conf = OmegaConf.create(s)从 dotlist 创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 输入 dotlist
dot_list = ["server.host=localhost", "server.port=8080", "database.user=admin"]
# 创建配置
conf = OmegaConf.from_dotlist(dot_list)
# 等价于创建:
# {
# "server": {
# "host": "localhost",
# "port": 8080
# },
# "database": {
# "user": "admin"
# }
# }- 注:Dotlist(点号列表)是 OmegaConf 中一种特殊的配置输入格式,使用点号分隔的路径来表示配置的层级结构,每个条目是一个 key=value 形式的字符串
从命令行参数创建
1
2# 假设命令行: python script.py server.port=82 log.file=log2.txt
conf = OmegaConf.from_cli()创建空配置
1
conf = OmegaConf.create() # 返回 {}
OmegaConf 访问与修改配置
访问配置值
1
2
3
4
5
6
7
8# 点号访问(最常用)
host = conf.server.host
# 字典风格访问
port = conf["server"]["port"]
# get 方法(可指定默认值)
timeout = conf.get("server.timeout", 30)修改配置
1
2conf.server.port = 3307
conf.model.num_layers = 24- OmegaConf 支持在运行时动态修改配置
持久化保存配置
1
2
3
4
5
6# 导出:保存为 YAML 字符串
yaml_str = OmegaConf.to_yaml(conf)
# 导出:保存到 yaml 文件
with open('config_modified.yaml', 'w') as f:
OmegaConf.save(conf, f)
OmegaConf 配置合并
OmegaConf.merge()可以将多个配置源合并为一个:1
2
3
4
5base_conf = OmegaConf.create({"server": {"port": 80, "host": "0.0.0.0"} })
override_conf = OmegaConf.create({"server": {"port": 8080} })
merged = OmegaConf.merge(base_conf, override_conf)
# 结果: {"server": {"port": 8080, "host": "0.0.0.0"} }- 合并规则:合并时,后面的配置会覆盖前面配置的同名字段。这对于处理不同环境(开发、测试、生产)的配置非常有用
常用示例:可以从新 YAML 文件加载配置来覆盖原始配置:
1
2
3cfg = OmegaConf.load('config.yaml')
override = OmegaConf.load('override.yaml')
cfg = OmegaConf.merge(cfg, override)
OmegaConf 插值(Interpolation)
插值允许在配置值中引用其他配置项
yaml 插值示例如下(OmegaConf 可以正确解析下面的 yaml 文件):
1
2
3
4# YAML 示例
server:
host: localhost
url: http://${server.host}:${server.port}OmegaConf 的直接插值示例(和 yaml 一样,可以非常灵活的使用):
1
2
3
4
5conf = OmegaConf.create({
"server": {"host": "localhost", "port": 8080},
"url": "http://${server.host}:${server.port}"
})
print(conf.url) # 输出: http://localhost:8080
OmegaConf 解析器(Resolvers)
内置解析器:oc.env for 环境变量
OmegaConf 内置了
oc.env解析器,可以用于直接读取环境变量yaml 使用示例(OmegaConf 可以正确解析下面的 yaml 文件):
1
2
3# YAML 示例
database:
password: ${oc.env:DB_PASSWORD,default_pass}OmegaConf 的直接使用解析器示例(和 yaml 一样,可以非常灵活的使用):
1
conf = OmegaConf.create({"db_pass": "${oc.env:DB_PASS,default}"})
注:使用时可以指定默认值,在环境变量未设置时使用
内置解析器:oc.select for 内置变量
OmegaConf 内置了
oc.select解析器,用于安全地 从当前配置树中按路径选取值,并在路径不存在时提供默认值YAML 使用示例(OmegaConf 可以正确解析下面的 YAML 文件):
1
2
3
4
5
6
7
8
9# YAML 示例
app:
# 如果 'env' 配置项存在,则使用其值;否则使用 'production' 作为默认值
environment: ${oc.select:env,production}
# 支持嵌套路径
database:
host: ${oc.select:db.host,localhost}
port: ${oc.select:db.port,3306}OmegaConf 的直接使用解析器示例(和 YAML 一样,可以非常灵活的使用):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 从配置树中安全获取值
conf = OmegaConf.create({
"env": "staging",
"app": {
"name": "${oc.select:env,production}-server"
}
})
assert conf.app.name == "staging-server"
# 嵌套路径访问
conf2 = OmegaConf.create({
"db": {"host": "192.168.1.1"}
})
host = OmegaConf.select(conf2, "db.host", default="localhost") # 返回 "192.168.1.1"
missing = OmegaConf.select(conf2, "db.port", default=3306) # 返回 3306注:使用时可以指定默认值,当配置路径不存在时使用
- 如果不提供默认值,路径不存在时会抛出
KeyError异常
- 如果不提供默认值,路径不存在时会抛出
自定义解析器
工作原理:自定义解析器的执行流程:
1
2
3
4
5
6
7# 1. 注册时:将名称与函数绑定
OmegaConf.register_resolver("my_func", my_function)
# 2. 解析时:OmegaConf 遇到 ${my_func:...} 时
# - 解析 : 后面的参数
# - 将参数传递给 my_function
# - 用返回值替换插值表达式自定义解析器
1
2
3
4
5
6
7
8
9
10
11
12def add(a, b):
return a + b
OmegaConf.register_resolver("add", add)
conf = OmegaConf.create({
"x": 5,
"y": 3,
"sum": "${add:${x},${y} }"
})
print(conf.sum) # 8自定义解析器默认参数用法
1
2
3
4
5
6
7
8
9
10
11
12
13def power(base, exponent=2):
return base ** exponent
OmegaConf.register_resolver("power", power)
conf = OmegaConf.create({
"base": 4,
"squared": "${power:${base} }", # 使用默认指数 2
"cubed": "${power:${base},3}" # 自定义指数 3
})
print(conf.squared) # 16
print(conf.cubed) # 64
OmegaConf 函数
OmegaConf.resolve()是 OmegaConf 库中一个用于强制解析配置中所有变量插值 的核心函数,用于 主动触发“计算”- 作用是将配置中所有
${...}形式的占位符,替换成它们实际引用的值或表达式的结果
- 作用是将配置中所有
注:在 OmegaConf 中,变量插值(Interpolation)默认是惰性(Lazy) 的,即只有在访问具体配置项时才会被解析
resolve()函数可以让你主动地、一次性地解析整个配置对象中的所有插值
resolve()的作用:- 确保数据完整性 :在序列化(如保存为 YAML 文件)或传递配置给其他不识别 OmegaConf 插值语法的函数前,可能希望得到一份所有值都已被“计算”好的、纯粹的配置
- 调试与查看 :想一眼看穿配置的最终全貌,而不是看到一堆
${...}引用
基本用法示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22from omegaconf import OmegaConf
# 1. 创建一个包含插值的配置
conf = OmegaConf.create({
"db": {
"host": "localhost",
"port": 3306
},
"service": {
# 这里的值引用了 db 中的配置
"connection_string": "mysql://${db.host}:${db.port}"
}
})
# 2. 此时,connection_string 的值还是未解析的插值字符串
print(conf.service.connection_string) # 输出: mysql://${db.host}:${db.port}
# 3. 调用 resolve() 强制解析所有插值
OmegaConf.resolve(conf)
# 4. 再次访问,值已被解析为最终结果
print(conf.service.connection_string) # 输出: mysql://localhost:3306- 注:解析是一次性的快照,解析以后,
db.host再发生改变,不会再影响connection_string
- 注:解析是一次性的快照,解析以后,
OmegaConf 结构化配置(Structured Configs)
使用
@dataclass定义配置类,可以获得运行时类型安全和 IDE 静态类型检查支持:1
2
3
4
5
6
7
8
9
10
11
12
13
14from dataclasses import dataclass
from omegaconf import OmegaConf
class MyConfig:
port: int = 80
host: str = "localhost"
conf = OmegaConf.structured(MyConfig)
print(conf.port) # 80
print(conf.host) # "localhost"
# 类型检查:以下操作会报错
conf.port = "8080" # 类型错误!- 结构化配置还可以与 IDE(如 Pyright、mypy)配合进行静态类型检查
附录:OmegaConf 实用工具函数
- OmegaConf 实用工具函数
函数 说明 OmegaConf.to_yaml(cfg)将配置转为 YAML 字符串 OmegaConf.to_container(cfg)将配置转为普通 dict/list OmegaConf.load(path)从文件加载配置 OmegaConf.save(cfg, path)保存配置到文件 OmegaConf.merge(cfg1, cfg2, ...)合并多个配置 OmegaConf.create(obj)从多种来源创建配置 OmegaConf.structured(cls)从数据类创建结构化配置 OmegaConf.from_dotlist(list)从点号列表创建配置 OmegaConf.from_cli()从命令行参数创建配置 OmegaConf.resolve()强制解析配置中所有变量插值,原地修改 - 注:
resolve()是原地操作 :它会直接修改传入的配置对象- 如果不想修改原对象,可以先使用
OmegaConf.create(cfg)复制一份再解析
- 如果不想修改原对象,可以先使用
OmegaConf.to_container(cfg, resolve=True)也可以实现类似resolve()的功能,但不是原地修改
补充:配置标志(Configuration Flags)
OmegaConf 支持在配置节点上设置 flag:
- 只读标志(readonly) :防止修改配置
- 结构体标志(struct) :防止创建不存在的字段
配置 flag 会从父节点继承
只读 flag 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from omegaconf import OmegaConf, read_write
conf = OmegaConf.create({"a": {"b": 10}})
# 开启只读模式
OmegaConf.set_readonly(conf, True)
# 尝试修改:会报错 ReadonlyConfigError
# conf.a.b = 20
# 可以使用 `read_write` 上下文管理器临时解除只读限制
with read_write(conf):
conf.a.b = 20 # 这次修改会成功
print(conf.a.b) # 输出: 20- 当对一个配置节点设置只读 flag 后,整个节点及其所有子节点都将无法被修改,任何修改尝试都会抛出 ReadonlyConfigError 异常
- 这在保护基础配置或关键参数时非常有用
- 当对一个配置节点设置只读 flag 后,整个节点及其所有子节点都将无法被修改,任何修改尝试都会抛出 ReadonlyConfigError 异常
结构体 flag 示例:防止创建不存在的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from omegaconf import OmegaConf, open_dict
conf = OmegaConf.create({"a": {"aa": 10, "bb": 20}})
# 开启结构模式
OmegaConf.set_struct(conf, True)
# 尝试添加新字段:会报错 ConfigAttributeError
# 开启结构体 flag 后,会防止创建不存在的字段
# conf.a.cc = 30
# 可以使用 `open_dict` 上下文管理器临时允许添加新字段
with open_dict(conf): # 注:亲测不使用 OmegaConf.set_struct(conf, True) 也能使用 with open_dict(conf)
conf.a.cc = 30 # 这次操作会成功
print(conf.a.cc) # 输出: 30- OmegaConf 默认允许随时创建新的配置字段
- 开启结构标记后,配置将变成“结构化”的,即禁止添加不存在的字段,防止因拼写错误而意外引入新配置项