Hydra Compose API 实战:在代码中直接组合配置(从 `hydra.experimental.compose` 到现代 `hydra.compose`)
Hydra Compose API 实战在代码中直接组合配置从hydra.experimental.compose到现代hydra.compose【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydraHydra 0.11.0 引入了实验性的 Compose API允许开发者在不通过命令行的情况下直接调用compose()函数组合出完整的配置对象。本文以该实验性 API 为切入点结合当前仓库Hydra 1.4.0.dev9的源码与示例系统讲解其初始化方式、函数签名、典型应用场景Jupyter Notebook、单元测试、Ray 并行调度与底层实现原理帮助你掌握代码即入口的配置组合能力。一、Compose API 是什么为什么需要它Hydra 的核心使用方式是hydra.main()装饰器应用从命令行解析 overrides、组合配置、管理输出目录与日志。但在某些场景下程序并没有命令行入口此时hydra.main()就不再适用。Compose API 正是为此而生它可以在代码的任何位置组合出一个与hydra.main()行为一致的配置对象且支持同样的 overrides 语法如dbmysql、db.userme。原文档明确指出调用compose()之前必须先完成 Hydra 的初始化初始化有两种途径——使用标准的hydra.main()或显式调用hydra.experimental.initialize()。从源码看这一约束被硬编码在 hydra/compose.py 中assert GlobalHydra().is_initialized(), ( GlobalHydra is not initialized, use hydra.main() or call one of the hydra initialization methods first )GlobalHydra是 Hydra 内部的全局单例见 hydra/core/global_hydra.py保存着当前进程唯一的Hydra实例compose()正是通过它拿到内部实现再调用hydra.compose_config()完成真正的配置组合。因此先初始化、后组合是使用 Compose API 的硬性前提。二、初始化方式initialize()与hydra.main()原文档给出initialize()的函数签名def initialize(config_dirNone, strictNone, caller_stack_depth1): Initializes the Hydra sub system :param config_dir: config directory relative to the calling script :param strict: Default value for strict mode :param caller_stack_depth: :return: 参数说明如下参数含义说明config_dir配置目录路径相对于调用脚本所在目录Hydra 运行时自动探测调用方脚本、模块、单元测试或 Jupyter Notebookstrictstrict 模式的默认值在 0.11 中用于控制是否允许访问未声明的配置键后续版本已由结构化配置Schema取代caller_stack_depth调用栈深度默认为 1即直接调用者用于在包装函数中定位真正的调用方另外也可以先运行hydra.main()装饰的应用函数再在其内部调用compose()——此时 Hydra 已由装饰器完成初始化。这也是 examples/advanced/ray_example/ray_compose_example.py 的做法hydra.main(config_pathconf, config_nameconfig) def main(cfg: DictConfig) - None: ray.init(**cfg.ray.init) results [] for model in [alexnet, resnet]: for dataset in [cifar10, imagenet]: overrides [fdataset{dataset}, fmodel{model}] run_cfg hydra.compose(overridesoverrides) # 在 hydra.main 内部直接组合 ret train.remote(overrides, run_cfg) results.append(ret)注意在hydra.main()内部调用compose()时config_name可省略会沿用主配置这样就能在同一个应用中为不同 overrides 组合出多份配置对象。三、核心用法示例原文档给出的完整示例0.11 写法导入路径为hydra.experimentalfrom hydra.experimental import compose, initialize if __name__ __main__: initialize( config_dirconf, strictTrue, ) cfg compose(config.yaml, overrides[dbmysql, db.userme]) print(OmegaConf.to_yaml(cfg))这段代码做了三件事initialize(config_dirconf, strictTrue)把conf目录加入配置搜索路径并设置 strict 默认值compose(config.yaml, overrides[dbmysql, db.userme])加载conf/config.yaml应用两条 override——将db配置组切换为mysql并把db.user字段覆盖为me将组合结果以 YAML 形式打印。仓库中对应的现代完整示例位于 examples/advanced/ad_hoc_composition/hydra_compose_example.py其配置目录 examples/advanced/ad_hoc_composition/conf/config.yaml 使用 defaults list 组合了三个配置组defaults: - db: mysql - ui: full - schema: school这是验证compose()行为的理想实验场在仓库根目录下运行python examples/advanced/ad_hoc_composition/hydra_compose_example.py即可看到dbmysql等 override 生效后的完整组合结果。示例中还展示了 override 与插值混用的能力db.user${oc.env:USER}会把环境变量USER解析进配置。四、compose()的参数与返回值原文档给出的compose()签名0.11def compose(config_fileNone, overrides[], strictNone): :param config_file: optional config file to load :param overrides: list of overrides for config file :param strict: optionally override the default strict mode :return: the composed config 在当前仓库中compose()已从hydra.experimental迁移到顶层hydra命名空间见 hydra/init.py签名也演化为def compose( config_name: Optional[str] None, overrides: Optional[List[str]] None, return_hydra_config: bool False, ) - DictConfig:参数0.11 名称现代名称作用主配置config_fileconfig_name要加载的配置文件名通常省略.yaml扩展名为None时在无默认配置的情况下返回空配置覆盖项overridesoverrides命令行式覆盖列表例如[dbmysql, db.userme]strict 模式strict已移除0.11 中用于覆盖初始化时的默认 strict 值返回 Hydra 配置—return_hydra_config为True时结果中保留hydra配置节点否则自动删除从 hydra/compose.py 可以看到return_hydra_config的底层行为当它为False默认时compose()会以open_dict方式把结果中的hydra节点删除使返回的DictConfig只包含用户业务配置方便直接序列化或断言。五、从实验 API 到正式 API版本演进脉络原文档所属的 0.11 版本将compose与initialize放在hydra.experimental包中作为实验特性。当前仓库1.4.0.dev9已经完成正式化迁移hydra.experimental.__init__.py目前仅保留版权声明实验命名空间已不再承载这两个函数顶层 hydra/init.py 将compose、initialize、initialize_config_module、initialize_config_dir全部列入__all__推荐导入方式为from hydra import compose, initialize from omegaconf import OmegaConf原先的strict参数被移除取而代之的是结构化配置Structured Configs提供的编译期校验能力initialize新增了version_base参数用于声明兼容级别详见 hydra/initialize.py配置路径参数也由config_dir更名为config_path。如果阅读 0.11 时代的老代码需要把hydra.experimental.compose替换为hydra.compose、config_dir替换为config_path并移除strict参数。六、三种初始化方法initialize、initialize_config_module、initialize_config_dir现代 Hydra 提供了三种初始化方法全部实现在 hydra/initialize.py它们既可作普通函数全局初始化仅调用一次也可作上下文管理器with块内生效可重复使用方法配置来源关键约束对应源码位置initialize(config_path...)相对路径路径相对于调用方所在目录Hydra 自动识别脚本/模块/测试/Notebookhydra/initialize.pyinitialize_config_module(config_module...)Python 包必须是可导入的绝对模块名顶层必须有__init__.py例如foo.bar.confhydra/initialize.pyinitialize_config_dir(config_dir...)文件系统绝对路径必须是绝对路径相对路径会直接抛出HydraExceptionhydra/initialize.py三者底层都通过Hydra.create_main_hydra_file_or_module()或Hydra.create_main_hydra2()构建内部 Hydra 实例并借助get_gh_backup()/restore_gh_from_backup()见 hydra/initialize.py保存与恢复GlobalHydra单例状态从而支持上下文管理器形式的多次安全初始化。作为上下文管理器使用的推荐写法全局初始化会被__exit__自动回滚多次调用互不干扰from hydra import compose, initialize from omegaconf import OmegaConf if __name__ __main__: with initialize(version_baseNone, config_pathconf, job_nametest_app): cfg compose(config_nameconfig, overrides[dbmysql, db.userme]) print(OmegaConf.to_yaml(cfg))七、典型应用场景1. Jupyter Notebook 中组合配置Notebook 环境没有命令行入口是 Compose API 最典型的应用场景。仓库提供了可交互的示例 examples/jupyter_notebooks/compose_configs_in_notebook.ipynb模式固定为initialize(config_pathconf) # 指向 Notebook 同级的 conf 目录 cfg compose(config_nameconfig) # 组合主配置注意Notebook 中每次重新执行初始化时需要先调用GlobalHydra.instance().clear()清空单例状态见 hydra/core/global_hydra.py或直接使用with initialize(...)上下文形式以避免重复初始化冲突。2. 单元测试中组合与断言配置在测试中组合配置可以验证 defaults list 与 overrides 的组合结果是否符合预期。官方测试模式详见 website/versioned_docs/version-1.3/advanced/unit_testing.mdfrom hydra import initialize, compose def test_with_initialize() - None: with initialize(version_baseNone, config_path../hydra_app/conf): cfg compose(config_nameconfig, overrides[app.usertest_user]) assert cfg { app: {user: test_user, num1: 10, num2: 20}, db: {host: localhost, port: 3306}, }这里的关键是initialize的config_path相对于调用该函数的测试文件定位配置目录所在包需要包含__init__.py以保证可导入性。3. 在无命令行访问权限的应用模块中使用对于没有标准hydra.main()入口的库或后台服务可以使用initialize()compose()获得代码级入口。仓库在 examples/advanced/ad_hoc_composition 中专门演示了这一模式并注释说明This is needed for apps that cannot have a standard hydra.main() entry point。4. 组合多份配置对象Ray 示例当需要为每个远程任务组合出不同的配置时可在hydra.main()内部循环调用compose(overrides...)见上文第三节引用的 examples/advanced/ray_example/ray_compose_example.py。八、底层原理compose 的完整调用链compose()的完整调用链可以概括为断言GlobalHydra已初始化未初始化直接抛异常通过GlobalHydra.instance()取得全局 Hydra 实例调用hydra.compose_config(config_name..., overrides..., run_modeRunMode.RUN, from_shellFalse, with_log_configurationFalse)见 hydra/_internal/hydra.py得到组合后的DictConfig若return_hydra_configFalse以open_dict方式删除hydra节点后返回。值得注意的是run_modeRunMode.RUN与from_shellFalse两个参数它们表明compose()始终以单次运行非 multirun模式、且非命令行来源的方式组合配置因此它不会触发命令行补全、多任务 sweep 或输出目录切换等hydra.main()才具备的副作用。仓库测试 tests/test_compose.py 对初始化与组合行为包括各种version_base的合法性与异常路径做了系统验证可以作为理解 API 边界的第一手参考。九、使用建议与限制优先使用hydra.main()官方文档明确建议凡能使用hydra.main()的场景就不要使用 Compose API否则会失去 Tab 补全、Multirun、工作目录管理与日志管理等一系列内置能力初始化次数作为全局函数使用时initialize()只能在进程内调用一次重复初始化会触发GlobalHydra的already initialized异常见 hydra/core/global_hydra.py如需重新初始化须先clear()路径语义config_path相对于调用方、config_module必须是可导入包、config_dir必须是绝对路径三种初始化方式的路径语义差异是实战中最容易踩坑的地方兼容性阅读或迁移 0.11 时代的hydra.experimental.compose代码时需同步替换导入路径、参数名并移除strict具体以当前仓库 hydra/initialize.py 与 hydra/compose.py 的实现为准。【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考