Hydra基础教程

基础使用

跑起来

安装包:pip install hydra-core
初始代码:

from omegaconf import DictConfig, OmegaConf  
import hydra  
  
# 使用hydra.main装饰器,指定配置文件路径和名称  
# config_path: 配置文件所在目录  
# config_name: 配置文件名称
@hydra.main(version_base=None, config_path=".", config_name="config")  
def my_app(cfg: DictConfig) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
if __name__ == "__main__":  
    my_app()

missing_value

对于???占位的,可以赋值后输出

@hydra.main(version_base=None, config_path=".", config_name="config")  
def my_app(cfg: DictConfig) -> None:  
    OmegaConf.resolve(cfg)  
    cfg.node.waldo = 11 # 给???node.waldo赋值,还可以在运行的时候通过命令行赋值  
    print(cfg.node.waldo)  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()

cli命令行输入

# 没有配置文件,只使用命令行输入参数,命令行输入参数的格式为:python my_app.py ++key=value  
# 必须加上++,要不然参数传不进去  
# python my_app.py ++db=10  
@hydra.main(version_base=None)  
def my_app(cfg: DictConfig) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()

配置文件组

文件是

- conf
  - db
    mysql.yaml
    postgresql.yaml
# python my_app.py +db=mysql  
# 通过命令行输入参数,指定配置文件路径,格式为:python my_app.py +db=mysql  
# +db=mysql 表示在配置文件中添加一个新的配置项 db,并将其值设置为 mysql  
# 因为只指定了config_path的值
# 外面这个db文件夹就是一个配置文件组,里面有mysql.yaml和postgresql.yaml两个配置文件,分别对应不同的数据库配置  
@hydra.main(config_path="conf")  
def my_app(cfg: DictConfig) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()
操作符 行为 如果键 不存在 如果键 已存在
+ (加号) 添加, 即创建新键 ✅ 成功 (创建新键) ❌ 失败 (报错)
++ (双加号) 强制覆盖或添加 ✅ 成功 (创建新键) ✅ 成功 (覆盖旧值)
一个问题:Hydra 的代码怎么知道 +db=mysql 里的 db 是一个配置组(要去文件夹里找文件),而不是一个普通字段(直接赋个字符串值)?

Hydra会自动去找,知道配置目录是conf,那就会找db文件夹,找到了就说明是配置组,没找到就是普通文件。

如果 Hydra 在 conf/ 目录下找不到 db 这个文件夹(也就是不存在名为 db 的配置组),那么 +db=mysql 就会被当作一个普通的字段赋值,最终 cfg.db 的值就是字符串 "mysql"

默认配置

目录结构:

- conf
  - db
    mysql.yaml
    sql.yaml
  - host
    a.yaml
    a2.yaml
config.yaml

config.yaml

# 有多个组时,在这里配置默认值,用户可以在运行时覆盖这些值  
  
# 所以此时的config.yaml就是新的配置文件  
defaults:  
  - db: mysql
  - host: a

代码配置

@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()

配置的继承

- conf
  config.yaml
  - db
    base_mysql.yaml
    继承本组.yaml
    继承其他组.yaml
  - db_schema
    base_mysql.yaml

同组可以直接继承某个yaml文件的配置
base_mysql.yaml

host: localhost  
port: 3306  
user: ???  
password: ???

同组继承:(直接覆盖参数或者添加)

# 继承同组配置,直接引用  
defaults:  
  - base_mysql  
  
user: omry  
password: secret  
port: 3307  
encoding: utf8

继承其他组的配置:

# 继承另一个组的配置  
# 格式:组路径/组名@_here_  
# @here_表示: 当前组继承自另一个组,不加就是插入,而不是覆盖继承  
defaults:  
  - /db_schema/base_mysql@_here_  
  
user: omry  
password: secret  
port: 3307  
encoding: utf8

实验组

实验组的意思是:config中设置了默认的配置,但是我们有其他常用的东西,我们可以创建一个实验组,然后在命令行中指定这个实验组,这样,我们就不需要每次都去修改配置文件了

比如db组合server组,不同的数据库配置不同的server服务器。
我们可以在默认config中写最常用的db+server,但是其他的配置组合可以单独写到experient组中,需要的时候导入。

package指令

很多时候,你需要在一个程序里使用两份相同类型但不同角色的配置。比如:从数据库 A 读数据,写到数据库 B。两个数据库的配置结构是一样的(都有 hostportuser 等),但值不同。

如果用普通的 - db: mysql,你只能生成一个 cfg.db,没法同时得到两个独立的配置块。

而 db@source: mysql 和 db@destination: mysql 这种语法就能完美解决:它把同一个 db 配置组(mysql.yaml加载两次,并分别放到 source 和 destination 这两个不同的键下面。

@hydra.main(version_base=None, config_path="conf", config_name="two_packages")  
def my_app(cfg: DictConfig) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
# 配置文件内容如下:  
# defaults:  
#   - db@source: mysql  
#   - db@destination: mysql  
# 意思是:source和destination都使用mysql,之后文件中就有两个配置项source和destination  
if __name__ == "__main__":  
    my_app()

db@source: mysql

  • db:配置组的名字(对应 conf/db/ 文件夹)。
  • mysql:选项的名字(对应 conf/db/mysql.yaml 文件)。
  • @source安装路径。意思是:“不要把加载的内容放在默认的 db 键下,而是放到 source 键下。”

如果不写 @source,默认 - db: mysql 会把内容放到 cfg.db
加上 @source 后,内容会被放到 cfg.source;同理,db@destination: mysql 会把内容放到 cfg.destination

所以最后算是一个文件被导入两次,成了两个不同的对象,便于分开操作。

instantiate实例化

通过配置来动态创建对象。
核心就是 hydra.utils.instantiate 这个函数。它可以根据配置yaml中的 _target_ 字段,自动找到对应的类,并用其余字段作为参数来创建该类的实例。

yaml文件:

# _target_的作用:告诉Hydra如何实例化这个类  
_target_: my_app.MySQLConnection  
host: localhost  
user: root  
password: 1234
# 抽象基类
class DBConnection:  
    def connect(self) -> None:  
        ...  
  
  
class MySQLConnection(DBConnection):  
    def __init__(self, host: str, user: str, password: str) -> None:  
        self.host = host  
        self.user = user  
        self.password = password  
  
    def connect(self) -> None:  
        print(f"MySQL connecting to {self.host}")  
  
  
class PostgreSQLConnection(DBConnection):  
    def __init__(self, host: str, user: str, password: str, database: str) -> None:  
        self.host = host  
        self.user = user  
        self.password = password  
        self.database = database  
  
    def connect(self) -> None:  
        print(f"PostgreSQL connecting to {self.host}")  
  
  
@hydra.main(version_base=None, config_path="conf", config_name="config")  
def my_app(cfg: DictConfig) -> None:  
    # instantiate函数的作用是:根据配置文件中的内容,创建一个对象  
    # 配置文件必须有_target_字段,才能被instantiate函数识别,_target_字段的值是一个字符串,表示要创建的对象的类名  
    connection = instantiate(cfg.db)  
    connection.connect()  
  
  
if __name__ == "__main__":  
    my_app()

instantiate(cfg.db) 大致做了这些事情:

  1. 读取 cfg.db 中的 _target_ 字段,它的值是一个字符串,比如 "__main__.MySQLConnection"
  2. 通过 Python 的导入机制找到这个类。
  3. 将 cfg.db 中 除了 _target_ 以外的所有字段 作为关键字参数,传递给该类的构造函数。
  4. 返回构造好的实例。
    上面代码相当于执行:MySQLConnection(host="localhost", user="root", password="secret")

partial instantiate 部分实例化

model:  
  _target_: my_app.Model  
  optim_partial:  
    _partial_: true  
    _target_: my_app.Optimizer  
    algo: SGD

Optimizer类,algo和lr参数,配置文件中想先只固定algo,lr先不固定,等创建的时候再说。

# 优化器类Optimizer的参数algo和lr,在yaml中定义的时候,加上_partial_: true,表示只实例化部分参数,其他参数在Model类中实例化的时候再传入,这样就可以实现部分实例化了  
# 这里是优化器的类Optimizer,包含了algo和lr两个参数,在yaml中定义的时候,只实例化了algo参数,lr参数在Model类中实例化的时候传入,这样就可以实现部分实例化了  
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):  
        super().__init__()  
        self.optim = optim_partial(lr=0.1)  
  
    def __repr__(self) -> str:  
        return f"Model(Optimizer={self.optim})"  
  
# 部分实例化,不想全实例化,只实例化部分参数  
# yaml中加上_partial_: true,表示只实例化部分参数  
@hydra.main(version_base=None, config_path=".", config_name="config")  
def my_app(cfg: DictConfig) -> None:  
    model = instantiate(cfg.model)  
    print(model)  
  
  
if __name__ == "__main__":  
    my_app()
  1. instantiate(cfg.model)
    • 创建 Model 实例。
    • 在创建 Model 时,需要传入 optim_partial 参数。
    • 这个 optim_partial 参数来自配置中的子字典,它包含 _target__partial_: truealgo: SGD
  2. instantiate 处理 _partial_: true
    • 因为它看到了 _partial_: true,所以 不会立即创建 Optimizer 对象
    • 相反,它返回一个 可调用对象(一个 partial 函数),这个可调用对象已经绑定了 algo='SGD',但还没有绑定 lr
    • 返回值类似于:partial(Optimizer, algo='SGD')
  3. 将这个可调用对象赋值给 optim_partial
    在 Model 的构造函数中:
    • optim_partial 就是上面那个 partial 函数。
    • 调用 optim_partial(lr=0.1) 时,它会将 lr=0.1 补充进去,然后完整调用 Optimizer(algo='SGD', lr=0.1),最终得到真正的 Optimizer 实例。
    • 这个实例被赋值给 self.optim
  4. 最终打印 model

递归实例化(Recursive Instantiation)

car:  
  _target_: my_app.Car  
  driver:  
    _target_: my_app.Driver  
    name: James Bond  
    age: 7  
  wheels:  
    - _target_: my_app.Wheel  
      radius: 20  
      width: 1  
    - _target_: my_app.Wheel  
      radius: 20  
      width: 1  
    - _target_: my_app.Wheel  
      radius: 20  
      width: 1  
    - _target_: my_app.Wheel  
      radius: 20  
      width: 1

递归创建,代码只写了一次,但是wheels四个配置,直接创建了四个

class Driver:  
    def __init__(self, name: str, age: int) -> None:  
        self.name = name  
        self.age = age  
  
  
class Wheel:  
    def __init__(self, radius: int, width: int) -> None:  
        self.radius = radius  
        self.width = width  
  
  
class Car:  
    def __init__(self, driver: Driver, wheels: List[Wheel]):  
        self.driver = driver  
        self.wheels = wheels  
  
    def drive(self) -> None:  
        print(f"Driver : {self.driver.name}, {len(self.wheels)} wheels")  
  
  
@hydra.main(version_base=None, config_path=".", config_name="config")  
def my_app(cfg: DictConfig) -> None:  
    car: Car = instantiate(cfg.car)  
    car.drive()  
  
  
if __name__ == "__main__":  
    my_app()

结构化配置StructuredConfig和dataclass结合

通过 ConfigStore 将 Python 的 dataclass 注册为配置,从而完全替代 YAML 配置文件

@dataclass  
class MySQLConfig:  
    host: str = "localhost"  
    port: int = 3306  
  
# 作用:创建一个ConfigStore对象,并将MySQLConfig类注册到ConfigStore中,以便在Hydra应用程序中使用。  
cs = ConfigStore.instance()  
# 注册MySQLConfig类到ConfigStore中,指定名称为"config",以便在Hydra应用程序中通过名称引用该配置类。  
cs.store(name="config", node=MySQLConfig)  
  
# 使用@hydra.main装饰器,指定配置文件名称为"config",并将MySQLConfig类作为参数传递给my_app函数,以便在函数中使用配置对象。  
@hydra.main(version_base=None, config_name="config")  
def my_app(cfg: MySQLConfig) -> None:  
    print(f"Host: {cfg.host}, port: {cfg.port}")  
  
  
if __name__ == "__main__":  
    my_app()

Hydra 结构化配置的嵌套用法:用多个 dataclass 组合成一个大的配置类,并通过 ConfigStore 注册,从而完全替代 YAML 文件。
相当于一个类中有俩子类

@dataclass  
class MySQLConfig:  
    host: str = "localhost"  
    port: int = 3306  
  
  
@dataclass  
class UserInterface:  
    title: str = "My app"  
    width: int = 1024  
    height: int = 768  
  
  

@dataclass  
class MyConfig:  
    db: MySQLConfig = MySQLConfig()  
    ui: UserInterface = UserInterface()  
  
  
cs = ConfigStore.instance()  
cs.store(name="config", node=MyConfig)  
  
# 这里直接调用的是和dataclass结合的配置文件类MyConfig,cfg就是MyConfig的实例了,可以直接访问它的属性  
@hydra.main(version_base=None, config_name="config")  
def my_app(cfg: MyConfig) -> None:  
    print(f"Title={cfg.ui.title}, size={cfg.ui.width}x{cfg.ui.height} pixels")  
  
  
if __name__ == "__main__":  
    my_app()

配置组

  • db: AnyConfig 中的 db 字段类型是 Any,可以接受任意类型的对象。
  • 命令行必须指定python my_app.py +db=mysql
    因为 Config.db 没有默认值,Hydra 不知道要加载哪个数据库配置,所以必须通过命令行 +db=mysql 告诉它选择 "db" 组下的 mysql 选项。
@dataclass  
class MySQLConfig:  
    driver: str = "mysql"  
    host: str = "localhost"  
    port: int = 3306  
  
  
@dataclass  
class PostGreSQLConfig:  
    driver: str = "postgresql"  
    host: str = "localhost"  
    port: int = 5432  
    timeout: int = 10  
  
  
@dataclass  
class Config:  
    # 这里的db字段被注解为Any类型,这意味着它可以接受任何类型的值。  
    # 我们是在命令行中输入参数:python my_app.py +db=mysql,这样Hydra会根据输入的参数来决定db字段的具体类型。  
    db: Any  
  
  
cs = ConfigStore.instance()  
cs.store(name="config", node=Config)  
# 注册MySQLConfig类到ConfigStore中,指定名称为"mysql",以便在Hydra应用程序中通过名称引用该配置类。  
# 指定group="db"表示这个配置类属于"db"组,这样在使用时可以通过"group=db, name=mysql"来引用它。  
cs.store(group="db", name="mysql", node=MySQLConfig)  
cs.store(group="db", name="postgresql", node=PostGreSQLConfig)  
  
  
@hydra.main(version_base=None, config_name="config")  
def my_app(cfg: Config) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()

配置继承
这里很简单,继承类就是继承配置了

@dataclass  
class DBConfig:  
    host: str = "localhost"  
    port: int = MISSING  
    driver: str = MISSING  
  
  
@dataclass  
class MySQLConfig(DBConfig): # 这是继承父类DBConfig  
    driver: str = "mysql"  
    port: int = 3306  
  
  
@dataclass  
class PostGreSQLConfig(DBConfig):  
    driver: str = "postgresql"  
    port: int = 5432  
    timeout: int = 10  
  
  
@dataclass  
class Config:  
    # We can now annotate db as DBConfig which  
    # improves both static and dynamic type safety.    db: DBConfig  
  
  
cs = ConfigStore.instance()  
cs.store(name="config", node=Config)  
cs.store(group="db", name="mysql", node=MySQLConfig)  
cs.store(group="db", name="postgresql", node=PostGreSQLConfig)  
  
# python my_app_with_inheritance.py +db=mysql  
@hydra.main(version_base=None, config_name="config")  
def my_app(cfg: Config) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()

defaults为配置组提供默认值

@dataclass  
class MySQLConfig:  
    driver: str = "mysql"  
    host: str = "localhost"  
    port: int = 3306  
    user: str = "omry"  
    password: str = "secret"  
  
  
@dataclass  
class PostGreSQLConfig:  
    driver: str = "postgresql"  
    host: str = "localhost"  
    port: int = 5432  
    timeout: int = 10  
    user: str = "postgres_user"  
    password: str = "drowssap"  
  
  
defaults = [  
    # config group name db will load config named mysql  
    {"db": "mysql"}  
]  
  
  
@dataclass  
class Config:  
    # this is unfortunately verbose due to @dataclass limitations  
    # 这里有显式的defaults字段,Hydra会根据defaults列表来填充db字段  
    # 所以命令行调用的时候,不用+,python my_app.py db=mysql  
    defaults: List[Any] = field(default_factory=lambda: defaults)  
  
    # Hydra will populate this field based on the defaults list  
    db: Any = MISSING  
  
  
cs = ConfigStore.instance()  
cs.store(group="db", name="mysql", node=MySQLConfig)  
cs.store(group="db", name="postgresql", node=PostGreSQLConfig)  
cs.store(name="config", node=Config)  
  
  
@hydra.main(version_base=None, config_name="config")  
def my_app(cfg: Config) -> None:  
    print(OmegaConf.to_yaml(cfg))  
  
  
if __name__ == "__main__":  
    my_app()
posted @ 2026-05-04 09:01  Changersh  阅读(20)  评论(0)    收藏  举报