Hydra: Python中的多配置环境管理

Hydra

Hydra是一个开源的Python配置管理框架,使用它可以简化多环境应用的开发。简单来说,我们可以将不同环境的配置写在不同的yaml配置文件中,使用Hydra可以帮助我们方便地加载和组合不同的配置,同时还支持使用命令行进行动态配置覆盖。Hydra的官方文档地址为:Getting started | Hydra,可以通过pip进行安装:

1
pip install hydra-core

所谓配置管理,实际上就可以理解为如何将对应的配置(key-value对)以dict的形式加载到我们的程序中,让程序能够动态访问。我们接下来也就会重点关注hydra如何能够做到这一点。

Quick Start

在hydra中,配置通过yaml配置文件来进行管理。例如我们可以使用数据库操作的场景。通常,我们会将配置文件放在单独的conf目录下,同时在配置文件中提供对应的信息,例如:

1
2
3
4
5
6
conf/config.yaml

db:
driver: mysql
user: your_name
pass: your_pass

此时,我们可以通过python代码hydra-test.py进行配置的访问,只需要调用hydra提供的注解,并且指定配置存放的目录以及配置的名称即可:

1
2
3
4
5
6
7
8
9
10
11
import hydra
from omegaconf import DictConfig


@hydra.main(version_base=None, config_path='conf', config_name='config')
def main(cfg: DictConfig):
print(cfg)


if __name__ == '__main__':
main()

这里python文件与conf在同级目录下。如果直接直接运行,可以得到如下输出,可以看到配置以多层嵌套dict的方式被读入程序中。

1
2
$ python hydra-test.py 
{'db': {'driver': 'mysql', 'user': 'your_name', 'pass': 'your_pass'}}

上面的hydra注解会给main函数传入一个DictConfig的cfg,我们可以将其理解为一个嵌套的多层字典。我们可以通过cfg.db.driver的attribute style方式调用,也可以通过cfg['db']['driver']的dictionary style方式调用。并且从配置文件到DictConfig到过程中存在类型推断。

同时,我们还可以在运行程序时进行配置的覆盖。例如下面的运行命令,会使用命令行中指定的配置value覆盖文件中的值:

1
2
$ python hydra-test.py db.user=root db.pass=1234
{'db': {'driver': 'mysql', 'user': 'root', 'pass': 1234}}

在使用命令行的时候,需要注意符号的使用。如果是在配置文件中已经存在的配置,那么就直接在命令行中指定;如果是在配置文件中不存在的配置,那么需要使用+进行修饰;同时hydra还提供++,该符号能够同时兼容以上两种情况。

1
2
$ python hydra-test.py +db.time_out=10 db.user=cmd_user      
{'db': {'driver': 'mysql', 'user': 'cmd_user', 'pass': 'your_pass', 'time_out': 10}}
1
2
$ python hydra-test.py ++db.time_out=10 ++db.user=cmd_user
{'db': {'driver': 'mysql', 'user': 'cmd_user', 'pass': 'your_pass', 'time_out': 10}}

多配置文件组合

配置组

hydra提供配置组的概念,允许我们同时存放多个同层级的配置。在配置目录conf下,每个子目录代表一个配置组,配置组中是对应不同的配置,每个配置组中只能同时加载一个配置。例如下面的层次结构(hydra-test.py仍然是Quick Start中的程序,用于获取当前配置并输出):

1
2
3
4
5
6
7
├── conf
│ ├── config.yaml
│ └── db
│ ├── mysql.yaml
│ └── postgresql.yaml

└── hydra-test.py

配置文件的内容分别如下:

1
2
3
# conf/config.yaml
defaults:
- db: mysql
1
2
3
4
5
# conf/db/mysql.yaml

driver: mysql
user: mysql_user
pass: mysql_pass
1
2
3
4
5
# conf/db/postgresql.yaml

driver: postgresql
user: postgresql_user
pass: postgresql_pass

在config.yaml中,通过defaults来指定db配置组默认使用哪一个配置。defaults是hydra中的一个特殊保留指令,用来指定配置组的默认使用配置。

直接通过命令行运行,运行的就是db配置组中mysql的配置:

1
2
$ python hydra-test.py
{'db': {'driver': 'mysql', 'user': 'mysql_user', 'pass': 'mysql_pass'}}

同样我们可以在命令行指定db配置组使用postgresql。当然此时也同样支持使用命令行覆盖对应参数:

1
2
$ python hydra-test.py db=postgresql db.pass=cmd_pass
{'db': {'driver': 'postgresql', 'user': 'postgresql_user', 'pass': 'cmd_pass'}}

当然在原始入口conf/config.yaml配置文件中,我们也可以指定配置组中的属性。此时为了能够加载该属性,我们需要在defaults中增加_self_关键字,表示默认加载自身。此时需要注意覆盖关系,实际执行类似于按照顺序加载,在后面加载的属性会覆盖前面加载的属性。例如此时conf/config.yaml内容如下:

1
2
3
4
5
6
defaults:
- _self_
- db: mysql

db:
user: config_user

输出如下,此时后面的mysql配置就覆盖了config中的config_user。如果把_self_移动到后面,则会加载出config_user

1
2
$ python hydra-test.py
{'db': {'user': 'mysql_user', 'driver': 'mysql', 'pass': 'mysql_pass'}}

Packages

在Hydra配置组中,存在Package的概念。在默认情况下,Package即为该配置的目录结构,类似于Java中的Package。例如,config/db/mysql.yaml这个配置文件所属的Package就是config.dbPackage最终体现在得到的配置字典中,这也就意味着对于该配置文件来说,最终在程序中得到的配置dict中,它的前置层次就是config.db,而mysql.yaml中的键值对就在这个前置层次之下进行组织。Package可以通过使用defaults或者Packages指令来修改。

首先构造下面的场景,配置文件的组织形式如下:

1
2
3
4
5
6
├── server
│ ├── db
│ │ ├── mysql.yaml
│ │ └── sqlite.yaml
│ └── apache.yaml
└── config.yaml

每个配置文件内容如下:

1
2
3
4
5
6
# config.yaml

defaults:
- server/apache

debug: false
1
2
3
4
5
6
# server/apache.yaml

defaults:
- db: mysql

name: apache
1
2
# server/db/mysql.yaml
name: mysql
1
2
# server/db/sqlite.yaml
name: sqlite

此时如果我们正常运行程序,可以得到如下的字典结构,可以看到都是按照正常的层级结构进行组织的。

1
2
3
4
5
server:
db:
name: mysql
name: apache
debug: false

第一种改变Package的方式是通过在defaults列表中进行操作。使用@PACKAGE操作符,改变的是defaults列表引入的yaml所对应的pacakge。

例如下面:

1
2
3
4
5
6
# config.yaml

defaults:
- server/apache@admin

debug: false
1
2
3
4
5
6
# server/apache.yaml

defaults:
- db@backup: mysql

name: apache

输出的层级结构如下,可以看到在key结构中,原本的server变成了admin,db变成了backup

1
2
3
4
5
admin:
backup:
name: mysql
name: apache
debug: false

第二种改变pacakge的方式是在对应yaml文件的首行直接使用package指令,它直接指定该yaml对应的pacakge。

例如修改mysql.yaml内容如下,这

1
2
# @package foo.bar
name: mysql

得到的配置字典层级结构如下。

1
2
3
4
5
6
server:
name: apache
debug: false
foo:
bar:
name: mysql

在这种方式下,有一种常用的保存字是_global_,它表示直接将该yaml的层级提升到最顶级。

可以看到一共有三种方式影响package,它们的优先关系如下:

  1. 在defaults列表中指定的package
  2. 在yaml文件中指定的package
  3. 默认文件层级结构

Hydra: Python中的多配置环境管理
http://example.com/2024/01/14/Hydra-Python中的多配置环境管理/
作者
EverNorif
发布于
2024年1月14日
许可协议