Python Hydra 框架介绍

1. 介绍

Hydra 可以通过组合动态创建分层配置,并且通过配置文件和命令行对其进行覆盖。

关键特性:

  1. 可以从多个来源组合成分层配置
  1. 可以通过命令行指定或覆盖配置
  1. 通过单个命令运行多个带有不同参数的任务

2. 快速入门

2.1. 安装

pip install hydra-core --upgrade

2.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: 1234

2.3. 组合配置的示例

如果希望在两个不同的数据库间切换,那么可以创建一个名为 db 的配置组,将每个备选方案放在单独的配置文件中。程序的目录结构类似:

├── conf
│   ├── config.yaml
│   ├── db
│   │   ├── mysql.yaml
│   │   └── postgresql.yaml
└── my_app.py

下面是新配置:

conf/config.yaml:

defaults:
  - db: mysql

conf/db/mysql.yaml:

driver: mysql
user: omry
pass: secret

conf/db/postgresql.yaml:

driver: postgresql
pass: drowssap
timeout: 10
user: postgres_user

defaults 是一个特殊指令,它告诉 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_user

2.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_user

3. 使用 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/imagenet

Hydra 默认递归地初始化嵌套对象:

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. 参数转换策略

默认情况下,传递给目标对象的参数要么是基础类型(比如 intfloatbool 等),要么是 OmegaConf 容器(DictConfigListConfig)。与 dictlist 相比,OmegaConf 容器具有许多优势,包括通过属性访问键、作为数据类或属性类实例的鸭子类型,以及支持变量插值和自定义解析器。如果 instantiate 调用的目标可调用对象使用 OmegaConf 的特性,那么直接将 DictConfigListConfig 实例传给它更合理。

在许多场景中,希望给可调用对象传递常规的 Python dictlist,而非 DictConfigListConfig 实例。可以通过 _convert_ 改变 instantiate 的转换策略。支持的值如下:

  • "none":默认行为,使用 OmegaConf 容器。
  • "partial":将 OmegaConf 容器转换为 dictlist,但将结构化配置保留为 DictConfig 实例。
  • "object":将 OmegaConf 容器转换为 dictlist,但通过 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_ 值应为“点路径”,指向可通过 importgetattr 进行查找的可调用对象。如果使用 Python 的内置函数(比如 lenprint 或 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 3

3.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
    """
    ...