引言

Hydra是一款开源Python框架,专为简化科研项目及其他复杂应用的开发而设计。其核心优势在于支持动态构建分层配置体系——既可通过组合方式创建层级结构,又能借助配置文件和命令行进行灵活调整。该名称源自其“多头并进”的特性:就像水蛇分叉般能同时运行多个相似任务,完美诠释了“众手齐发力”的开发理念。

主要功能

  1. 支持多源组合的层次化配置管理:可从多个来源(文件、环境变量、命令行等)加载并组合配置,形成层次化的配置结构
  2. 命令行参数配置覆盖:支持通过命令行参数直接指定或覆盖配置值,提供灵活的配置方式
  3. 动态命令行补全:提供智能的命令行选项补全功能,提升开发体验和效率
  4. 本地与远程执行支持:支持在本地运行应用程序,或配置并启动远程执行环境
  5. 多参数作业并行执行:支持通过单个命令运行具有不同参数组合的多个作业,实现高效的参数空间探索

快速入门

模块安装

1
pip install hydra-core --upgrade

这里安装的时候要注意一下!!因为导入的包是hydra,所以会下意识的安装hydra,而不是hydra-core,导致安装不成功。(因为我自己去安装的时候有出现这个问题)

简单案例

1. 基本用法(命令行参数)

1
2
3
4
5
6
7
8
9
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None)
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
my_app()

在本示例中,Hydra创建一个空的cfg对象,并将其传递给带有@hydra.main注解的函数。

你可以通过命令行添加配置值,+表示该字段是新的。

1
python my_app.py +db.driver=mysql +db.user=omry +db.password=secret

当你在命令行中输入上述命令时,Hydra会将db.driverdb.userdb.password添加到cfg对象中。

你可以在Python代码中使用cfg对象来访问这些配置值。例如:

1
2
3
4
def my_app(cfg: DictConfig) -> None:
print(cfg.db.driver)
print(cfg.db.user)
print(cfg.db.password)

在本示例中,my_app函数接收一个DictConfig对象作为参数,该对象包含了从命令行添加的配置值。通过cfg.db.drivercfg.db.usercfg.db.password,你可以访问到相应的配置值。

2. 配置文件(config.yaml)

你也可以在配置文件中定义默认值。例如,在文件夹conf下创建一个config.yaml文件:

1
2
3
4
db:
driver: mysql
user: omry
password: secret

然后,在Python代码中使用@hydra.main注解时,指定配置文件的路径:

1
2
3
@hydra.main(version_base=None, config_path="./conf", config_name="config")
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))

当你启动应用的时候,Hydra会自动加载conf文件夹下的config.yaml文件。

config_path是配置文件的路径,config_name是配置文件的名称。注意:这里的config_path是相对于Python文件的路径,所以这里是./conf,如果是绝对路径,就不需要加./了。

您可以从命令行覆盖已加载配置中的值,不用加+作为前缀,直接写键值对即可

1
python my_app.py db.driver=postgresql

在本示例中,db.driver的值被覆盖为postgresql

你可以在命令行中添加多个配置值,它们会被合并到cfg对象中。

1
python my_app.py db.driver=postgresql db.user=postgres

在本示例中,db.driver的值被覆盖为postgresqldb.user的值被设置为postgres

如果配置中已存在某个配置值,则使用++来覆盖该值,否则添加该值。例如:

1
2
3
4
5
# 覆盖存在的配置值
$ python my_app.py ++db.password=1234

# 添加一个新的配置值
$ python my_app.py ++db.timeout=5

3.配置对象

上面两个小节主要展示了hydra的使用方式,一个是在命令行中读取,一个是在配置文件中读取。

下面这个小节主要展示了hydra的配置对象的这一特点。即可以通过对象的方式来访问配置文件中的值。

1
2
3
4
5
6
7
node:                         # yaml的配置是分层的
loompa: 10 # 简单值
zippity: ${node.loompa} # 插值,这里是引用node.loompa的值

do: "oompa ${node.loompa}" # 字符串插值,这里是引用node.loompa的值

waldo: ??? # 缺失值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None, config_path=".", config_name="config")
def my_app(cfg: DictConfig):
assert cfg.node.loompa == 10 # 属性方式访问
assert cfg["node"]["loompa"] == 10 # 字典方式访问
assert cfg.node.zippity == 10 # 插值方式访问
assert isinstance(cfg.node.zippity, int) # 插值方式访问的值是int类型
assert cfg.node.do == "oompa 10" # 字符串插值方式访问
cfg.node.waldo # 缺失值访问,会报错


if __name__ == "__main__":
my_app()

运行上面的代码报错是很正常的,因为waldo是一个缺失值,没有在配置文件中定义,所以会报错。要正确跑通只需要把cfg.node.waldo注释掉即可。

通过上面的例子可以了解到,hydra可以通过对象的方式对配置文件的键值对进行封装,从而可以通过使用对象访问属性的方式来获取配置值。

4.配置组文件

假如你想在PostgreSQL和MySQL这俩数据库上给应用程序做个基准测试,这时候就可以用配置组。

配置组,就是一个有一堆有效选项的命名组。要是你选了个不存在的配置选项,就会报错,报错信息里还会把有效的选项都列出来。

要创建配置组,请创建一个目录(例如db),用于存放每个数据库配置选项的文件。由于我们预期会有多个配置组,因此我们将所有配置文件主动移动到conf目录中。

示例的文件树如下:

1
2
3
4
5
├─ conf
│ └─ db
│ ├─ mysql.yaml
│ └─ postgresql.yaml
└── my_app.py

在mysql.yaml中,我们可以这样配置:

1
2
3
driver: mysql
user: omry
password: secret

在postgresql.yaml中,我们可以这样配置:

1
2
3
4
driver: postgresql
user: postgres_user
password: drowssap
timeout: 10

由于我们已将所有配置文件移至conf目录,因此需要通过config_path参数告知Hydra如何定位这些配置。config_path是相对于my_app.py的目录。

1
2
3
4
5
6
7
8
9
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None, config_path="conf")
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
my_app()

运行my_app.py而不请求配置将打印空的配置。

运行my_app.py而不请求配置将打印空的配置。这是因为我们没有在命令行中指定任何配置,也没有在配置文件中定义默认值。我们虽然在配置文件中定义了配置项,但是并没有说是使用db.mysql还是db.postgresql,所以打印为空。

从配置组中选择一个项,其+GROUP=OPTION,例如:

1
2
3
4
5
6
$ python my_app.py +db=postgresql
db:
driver: postgresql
password: drowssap
timeout: 10
user: postgres_user

上面这个示例就是通过+db=postgresql来选择db.postgresql这个配置组。因此得到的就是postgresql.yaml文件中的配置项。反之同理,通过+db=mysql来选择db.mysql这个配置组。因此得到的就是mysql.yaml文件中的配置项。

可以在生成的配置中覆盖单个值,也就是带+和不带+的组合使用!!

1
2
3
4
5
6
$ python my_app.py +db=postgresql db.user=postgres
db:
driver: postgresql
password: drowssap
timeout: 10
user: postgres

上面这个示例就是先选择db.postgresql这个配置组,然后在这个配置组中覆盖db.user这个配置项的值为postgres。因此得到的就是postgresql.yaml文件中的配置项,但是user的值被覆盖为postgres

5.选择默认的配置

通过上述配置组文件的案例我们可以知道,我们需要通过+db=mysql来选择db.mysql这个配置组,不选择的时候返回的是空的。但是hydra可以在config.yaml中定义默认值,这样就可以在不指定+db的情况下,默认选择db.mysql这个配置组。

我们只需要创建一个config.yaml文件,在这个文件中定义默认值,例如:

1
2
defaults:
- db: mysql

请记住要指定config_name:

1
2
3
4
5
6
7
8
9
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None, config_path="conf", config_name="config")
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
my_app()

运行更新后的应用程序时,默认情况下将加载MySQL。

1
2
3
4
5
$ python my_app.py
db:
driver: mysql
password: secret
user: omry

默认值列表中可以包含多个项目,例如:

1
2
3
defaults:
- db: mysql
- db/mysql/engine: innodb

如果存在多个默认值,那么默认值按以下顺序排列:

  • 当多个配置中定义了相同的配置项时,按配置加载顺序,最后加载的配置项值会覆盖之前加载的配置项值。
  • 当多个配置文件对同一个字典进行配置时,按配置文件加载顺序,最后加载的配置文件中的字典值会覆盖之前配置文件中的字典值。

可以通过在默认值列表中条目的前缀添加~来删除该条目:

1
2
$ python my_app.py ~db
{}

总结

Hydra作为一款强大的配置管理框架,为复杂应用程序和科研项目提供了优雅的配置解决方案。其核心优势在于支持层次化、多源组合的配置管理,同时提供灵活的命令行参数覆盖和智能补全功能,极大提升了开发效率和配置灵活性。Hydra通过其强大而灵活的配置管理能力,帮助开发者从繁琐的配置工作中解放出来,更专注于核心业务逻辑和算法创新,是现代复杂应用开发和科研实验管理的得力工具。

其实我发现这个模块也是因为看了Time-R1这个项目的源码才知道的,这个项目中使用了hydra来管理配置文件,所以我才去学习了一下hydra的使用🌈。所以学习源码还是有用的,能从中发现一些很有趣且有用的模块,帮助提升自己的开发和研发能力,提高效率!!