Python Hydra 框架介绍
1. 介绍
Hydra 可以通过组合动态创建分层配置,并且通过配置文件和命令行对其进行覆盖。
关键特性:
- 可以从多个来源组合成分层配置
- 可以通过命令行指定或覆盖配置
- 通过单个命令运行多个带有不同参数的任务
2. 快速入门
2.1. 安装
pip install hydra-core --upgrade2.2. 基础示例
配置(conf/config.yaml):
db:
driver: mysql
user: omry
pass: secret程序(my_app.py):
import hydra
from omegaconf import DictConfig, OmegaConf
@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()在运行程序时将自动加载 config.yaml:
$ python my_app.py
db:
driver: mysql
pass: secret
user: omry可以通过命令行覆盖配置中的值:
$ python my_app.py db.user=root db.pass=1234
db:
driver: mysql
user: root
pass: 12342.3. 组合配置的示例
如果希望在两个不同的数据库间切换,那么可以创建一个名为 db 的配置组,将每个备选方案放在单独的配置文件中。程序的目录结构类似:
├── conf
│ ├── config.yaml
│ ├── db
│ │ ├── mysql.yaml
│ │ └── postgresql.yaml
└── my_app.py下面是新配置:
conf/config.yaml:
defaults:
- db: mysqlconf/db/mysql.yaml:
driver: mysql
user: omry
pass: secretconf/db/postgresql.yaml:
driver: postgresql
pass: drowssap
timeout: 10
user: postgres_userdefaults 是一个特殊指令,它告诉 Hydra 在组合配置对象时使用 db/mysql.yaml。最终的 cfg 对象是 defaults 配置与 config.yaml 中指定的配置的组合。
现在可以选择使用哪个数据库配置,以及通过命令行覆盖值:
$ python my_app.py db=postgresql db.timeout=20
db:
driver: postgresql
pass: drowssap
timeout: 20
user: postgres_user2.4. Multirun
通过使用 --multirun 或 -m 标志,可以轻松地用不同的配置多次运行同一个函数:
$ python my_app.py --multirun db=mysql,postgresql
[2026-04-04 11:16:18,595][HYDRA] Launching 2 jobs locally
[2026-04-04 11:16:18,595][HYDRA] #0 : db=mysql
db:
driver: mysql
user: omry
pass: secret
[2026-04-04 11:16:18,619][HYDRA] #1 : db=postgresql
db:
driver: postgresql
pass: drowssap
timeout: 20
user: postgres_user3. 使用 Hydra 实例化对象
在程序中驱动不同行为的最佳方式之一是实例化接口的不同实现。使用实例化对象的代码只了解维持不变的接口,但行为由真正的对象实例决定。
Hydra 提供 hydra.utils.instantiate()(及其别名 hydra.utils.call())用于实例化对象和调用函数。创建对象时建议使用 instantiate,调用函数时则建议使用 call。
call/instantiate 支持:
- 通过调用
__init__方法构造对象
- 调用函数、静态函数、类方法及其他可调用对象
传递给它们的配置必须包含名为 _target_ 的键,其值为完全限定的类名、类方法、静态方法或可调用对象。为方便起见,若配置为 None,则返回 None。
命名参数:配置中的字段(除 _target_ 之类的保留字段外)将作为命名参数传递给目标对象。可以通过在调用 instantiate() 时传入同名的命名参数覆盖配置中的命名参数。
位置参数:配置中可以包含 _args_ 字段,表示传递给目标对象的位置参数。可以通过在调用 instantiate() 时传入位置参数整体覆盖这些位置参数。
3.1. 简单使用
假设程序中有一个名为 Optimizer 的类:
class Optimizer:
algo: str
lr: float
def __init__(self, algo: str, lr: float) -> None:
self.algo = algo
self.lr = lr
def __str__(self) -> str:
return f"{self.__class__.__name__}(algo={self.algo}, lr={self.lr})"配置:
optimizer:
_target_: my_app.Optimizer
algo: SGD
lr: 0.01实例化:
opt = instantiate(cfg.optimizer)
print(opt)
# Optimizer(algo=SGD,lr=0.01)
# override parameters on the call-site
opt = instantiate(cfg.optimizer, lr=0.2)
print(opt)
# Optimizer(algo=SGD,lr=0.2)3.2. 递归实例化
下面添加 Dataset 和 Trainer 类,Trainer 持有 Dataset 和 Optimizer 实例:
class Dataset:
name: str
path: str
def __init__(self, name: str, path: str) -> None:
self.name = name
self.path = path
def __str__(self) -> str:
return f"{self.__class__.__name__}(name={self.name}, path={self.path})"
class Trainer:
def __init__(self, optimizer: Optimizer, dataset: Dataset) -> None:
self.optimizer = optimizer
self.dataset = dataset
def __str__(self) -> str:
return f"{self.__class__.__name__}(optimizer={self.optimizer}, dataset={self.dataset})"使用下面的配置,可以在单个调用中实例化所有东西:
trainer:
_target_: my_app.Trainer
optimizer:
_target_: my_app.Optimizer
algo: SGD
lr: 0.01
dataset:
_target_: my_app.Dataset
name: Imagenet
path: /datasets/imagenetHydra 默认递归地初始化嵌套对象:
trainer = instantiate(cfg.trainer)
print(trainer)
# Trainer(
# optimizer=Optimizer(algo=SGD,lr=0.01),
# dataset=Dataset(name=Imagenet, path=/datasets/imagenet)
# )可以覆盖嵌套对象的参数:
trainer = instantiate(
cfg.trainer,
optimizer={"lr": 0.3},
dataset={"name": "cifar10", "path": "/datasets/cifar10"},
)
print(trainer)
# Trainer(
# optimizer=Optimizer(algo=SGD,lr=0.3),
# dataset=Dataset(name=cifar10, path=/datasets/cifar10)
# )3.3. 禁用递归实例化
可以通过在配置节点中或调用 instantiate 时将 _recursive_ 设置为 False 的方式,禁用递归初始化。此时,对于嵌套的 Dataset 和 Optimizer,Trainer 对象将接收到 OmegaConf DictConfig,而非实例化的对象。
trainer = instantiate(
cfg.trainer,
_recursive_=False,
)
print(trainer)输出:
Trainer(optimizer={'_target_': 'my_app.Optimizer', 'algo': 'SGD', 'lr': 0.01}, dataset={'_target_': 'my_app.Dataset', 'name': 'Imagenet', 'path': '/datasets/imagenet'})3.4. 参数转换策略
默认情况下,传递给目标对象的参数要么是基础类型(比如 int、float、bool 等),要么是 OmegaConf 容器(DictConfig、ListConfig)。与 dict 和 list 相比,OmegaConf 容器具有许多优势,包括通过属性访问键、作为数据类或属性类实例的鸭子类型,以及支持变量插值和自定义解析器。如果 instantiate 调用的目标可调用对象使用 OmegaConf 的特性,那么直接将 DictConfig 和 ListConfig 实例传给它更合理。
在许多场景中,希望给可调用对象传递常规的 Python dict 和 list,而非 DictConfig 和 ListConfig 实例。可以通过 _convert_ 改变 instantiate 的转换策略。支持的值如下:
"none":默认行为,使用 OmegaConf 容器。
"partial":将 OmegaConf 容器转换为dict和list,但将结构化配置保留为DictConfig实例。
"object":将 OmegaConf 容器转换为dict和list,但通过OmegaConf.to_object将结构化配置转换为对应的数据类或属性类实例。
"all":将所有东西都转换为基础容器类型。
转换策略将递归地应用于实例化目标的所有子配置。以下是演示不同转换策略的示例:
from dataclasses import dataclass
from omegaconf import DictConfig, OmegaConf
from hydra.utils import instantiate
@dataclass
class Foo:
a: int = 123
class MyTarget:
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
cfg = OmegaConf.create(
{
"_target_": "__main__.MyTarget",
"foo": Foo(),
"bar": {"b": 456},
}
)
obj_none = instantiate(cfg, _convert_="none")
assert isinstance(obj_none, MyTarget)
assert isinstance(obj_none.foo, DictConfig)
assert isinstance(obj_none.bar, DictConfig)
obj_partial = instantiate(cfg, _convert_="partial")
assert isinstance(obj_partial, MyTarget)
assert isinstance(obj_partial.foo, DictConfig)
assert isinstance(obj_partial.bar, dict)
obj_object = instantiate(cfg, _convert_="object")
assert isinstance(obj_object, MyTarget)
assert isinstance(obj_object.foo, Foo)
assert isinstance(obj_object.bar, dict)
obj_all = instantiate(cfg, _convert_="all")
assert isinstance(obj_all, MyTarget)
assert isinstance(obj_all.foo, dict)
assert isinstance(obj_all.bar, dict)将 _convert_ 关键字参数传递给 instantiate,与在配置对象上定义 _convert_ 属性的效果相同。以下示例创建与上面等效的 MyTarget 实例:
cfg_none = OmegaConf.create({..., "_convert_": "none"})
obj_none = instantiate(cfg_none)
cfg_partial = OmegaConf.create({..., "_convert_": "partial"})
obj_partial = instantiate(cfg_partial)
cfg_object = OmegaConf.create({..., "_convert_": "object"})
obj_object = instantiate(cfg_object)
cfg_all = OmegaConf.create({..., "_convert_": "all"})
obj_all = instantiate(cfg_all)3.5. 部分初始化
有时可能无法从配置中设置实例化目标对象所需的所有参数,在这种情况下,可以将 _partial_ 设为 True,获得由 functools.partial 包装的对象或方法,然后在程序代码中完成该对象的初始化。
my_app.py:
from typing import Any
import hydra
from hydra.utils import instantiate
from omegaconf import DictConfig, OmegaConf
class Optimizer:
algo: str
lr: float
def __init__(self, algo: str, lr: float) -> None:
self.algo = algo
self.lr = lr
def __repr__(self) -> str:
return f"Optimizer(algo={self.algo},lr={self.lr})"
class Model:
def __init__(self, optim_partial: Any, lr: float):
super().__init__()
self.optim = optim_partial(lr=lr)
self.lr = lr
def __repr__(self) -> str:
return f"Model(Optimizer={self.optim},lr={self.lr})"
@hydra.main(version_base=None, config_path="conf", config_name="config")
def my_app(cfg: DictConfig) -> None:
model = instantiate(cfg.model)
print(model)
if __name__ == "__main__":
my_app()conf/config.py:
model:
_target_: my_app.Model
optim_partial:
_partial_: true
_target_: my_app.Optimizer
algo: SGD
lr: 0.01如果反复实例化同一配置,与常规实例化相比,使用 _partial_=True 将显著提升速度。
factory = instantiate(cfg.model, _partial_=True)
obj = factory()
print(obj)在上述示例中,重复调用 factory 比重复调用 instantiate(config) 更快。使用这种方法需要注意,每次调用 factory 时,都重复使用相同的关键字参数。
class Foo:
...
class Bar:
def __init__(self, foo):
self.foo = foo
bar_conf = {
"_target_": "__main__.Bar",
"foo": {"_target_": "__main__.Foo"},
}
bar_factory = instantiate(bar_conf, _partial_=True)
bar1 = bar_factory()
bar2 = bar_factory()
assert bar1 is not bar2
assert bar1.foo is bar2.foo # 此处重复使用 Foo 实例如果 _partial_=False,则不适用上述情况。在这种情况下,每次调用 instantiate 都创建新的 Foo 实例。
3.6. 内置类型的初始化
传递给 instantiate 的 _target_ 值应为“点路径”,指向可通过 import 和 getattr 进行查找的可调用对象。如果使用 Python 的内置函数(比如 len、print 或 divmod),则需要提供在 Python 的 builtins 模块中查找该函数的点路径。
from hydra.utils import instantiate
# instantiate({"_target_": "len"}, [1,2,3]) # this gives an InstantiationException
instantiate({"_target_": "builtins.len"}, [1,2,3]) # this works, returns the number 33.7. 点路径查找机制
Hydra 查找给定 _target_ 的方式是:首先尝试查找与给定点路径的前缀部分对应的模块,然后在该模块中查找与点路径尾部对应的对象。比如,对于点路径 "my_module.my_nested_module.my_object",Hydra 先定位模块 my_module.my_nested_module,然后在该嵌套模块内部查找 my_object。
Hydra 提供直接使用点路径查找机制的 API。可以从 hydra.utils 模块导入以下 3 个函数,它们接受字符串类型的点路径作为参数,返回定位到的类/可调用对象/对象:
def get_class(path: str) -> type:
"""
Look up a class based on a dotpath.
Fails if the path does not point to a class.
>>> import my_module
>>> from hydra.utils import get_class
>>> assert get_class("my_module.MyClass") is my_module.MyClass
"""
...
def get_method(path: str) -> Callable[..., Any]:
"""
Look up a callable based on a dotpath.
Fails if the path does not point to a callable object.
>>> import my_module
>>> from hydra.utils import get_method
>>> assert get_method("my_module.my_function") is my_module.my_function
"""
...
# Alias for get_method
get_static_method = get_method
def get_object(path: str) -> Any:
"""
Look up a callable based on a dotpath.
>>> import my_module
>>> from hydra.utils import get_object
>>> assert get_object("my_module.my_object") is my_module.my_object
"""
...