Argument Prefixing

When nesting structures in tyro, arguments are prefixed with the parent field name to avoid naming collisions. For example, a field learning_rate inside a field called optimizer becomes --optimizer.learning-rate.

This page covers ways to control this prefixing behavior.

Hide code cell content

import dataclasses
from typing import Annotated, Union

import tyro

# Monkeypatch tyro.cli so --help prints without exiting.
_original_cli = tyro.cli


def _patched_cli(*args, **kwargs):
    try:
        return _original_cli(*args, **kwargs)
    except SystemExit:
        pass


tyro.cli = _patched_cli

# Force ANSI colors and UTF-8 box drawing characters in output.
tyro._fmtlib._FORCE_ANSI = True
tyro._fmtlib._FORCE_UTF8_BOXES = True

Arguments

Default: arguments are prefixed

Consider two nested dataclasses. The inner fields are automatically prefixed with the outer field name:

@dataclasses.dataclass
class OptimizerConfig:
    learning_rate: float = 1e-3
    weight_decay: float = 0.01


@dataclasses.dataclass
class Config:
    optimizer: OptimizerConfig
    epochs: int = 10


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] [OPTIONS]

╭─ options ─────────────────────────────────────╮
 -h, --help    show this help message and exit 
 --epochs INT  (default: 10)                   
╰───────────────────────────────────────────────╯
╭─ optimizer options ───────────────────────────╮
 --optimizer.learning-rate FLOAT               
               (default: 0.001)                
 --optimizer.weight-decay FLOAT                
               (default: 0.01)                 
╰───────────────────────────────────────────────╯

prefix_name=False: drop the parent’s prefix

tyro.conf.arg(prefix_name=False) on a field means “don’t prefix me with the prefix inherited from my parent.”

To see the effect, we need at least two levels of nesting. Below, prefix_name=False on optimizer drops the train. prefix from optimizer and its children, but optimizer itself still appears:

@dataclasses.dataclass
class OptimizerConfig:
    learning_rate: float = 1e-3


@dataclasses.dataclass
class TrainConfig:
    optimizer: Annotated[OptimizerConfig, tyro.conf.arg(prefix_name=False)]


@dataclasses.dataclass
class Experiment:
    train: TrainConfig


tyro.cli(Experiment, args=["--help"], prog="script.py")
usage: script.py [-h] [--optimizer.learning-rate FLOAT]

╭─ options ───────────────────────────────────╮
 -h, --help  show this help message and exit 
╰─────────────────────────────────────────────╯
╭─ optimizer options ─────────────────────────╮
 --optimizer.learning-rate FLOAT             
             (default: 0.001)                
╰─────────────────────────────────────────────╯

Note that --train.optimizer.learning-rate became --optimizer.learning-rate. The train. prefix was dropped, but optimizer still appears.

name="": suppress a field’s name from child prefixes

tyro.conf.arg(name="") suppresses the field’s own name from the prefix chain. Children are prefixed with whatever came above this field, skipping the field’s name entirely.

Here, name="" on optimizer removes optimizer. from all child argument names:

@dataclasses.dataclass
class OptimizerConfig:
    learning_rate: float = 1e-3
    weight_decay: float = 0.01


@dataclasses.dataclass
class Config:
    optimizer: Annotated[OptimizerConfig, tyro.conf.arg(name="")]
    epochs: int = 10


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] [--epochs INT] [--learning-rate FLOAT] [--weight-decay FLOAT]

╭─ options ──────────────────────────────────────────────╮
 -h, --help             show this help message and exit 
 --epochs INT           (default: 10)                   
 --learning-rate FLOAT  (default: 0.001)                
 --weight-decay FLOAT   (default: 0.01)                 
╰────────────────────────────────────────────────────────╯

Now --optimizer.learning-rate became just --learning-rate.

OmitArgPrefixes: strip prefixes from all descendants

tyro.conf.OmitArgPrefixes is a marker that strips prefixes added by the annotated field and all of its descendants. Any prefix accumulated from ancestors is preserved — only new prefixes below this point are omitted.

Here, OmitArgPrefixes on optimizer causes its children to appear without the optimizer. prefix:

from tyro.conf import OmitArgPrefixes


@dataclasses.dataclass
class OptimizerConfig:
    learning_rate: float = 1e-3
    weight_decay: float = 0.01


@dataclasses.dataclass
class Config:
    optimizer: OmitArgPrefixes[OptimizerConfig]
    epochs: int = 10


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] [--epochs INT] [--learning-rate FLOAT] [--weight-decay FLOAT]

╭─ options ──────────────────────────────────────────────╮
 -h, --help             show this help message and exit 
 --epochs INT           (default: 10)                   
╰────────────────────────────────────────────────────────╯
╭─ optimizer options ────────────────────────────────────╮
 --learning-rate FLOAT  (default: 0.001)                
 --weight-decay FLOAT   (default: 0.01)                 
╰────────────────────────────────────────────────────────╯

Unlike name="" which only suppresses one level, OmitArgPrefixes applies recursively — all descendants lose their prefixes, not just immediate children.

Subcommands

The same ideas apply to subcommands. By default, subcommand names are prefixed with the field name:

@dataclasses.dataclass
class Adam:
    learning_rate: float = 1e-3
    beta1: float = 0.9


@dataclasses.dataclass
class SGD:
    learning_rate: float = 1e-2
    momentum: float = 0.9


@dataclasses.dataclass
class Config:
    optimizer: Union[Adam, SGD]


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] {optimizer:adam,optimizer:sgd}

╭─ options ───────────────────────────────────────────╮
 -h, --help          show this help message and exit 
╰─────────────────────────────────────────────────────╯
╭─ subcommands ───────────────────────────────────────╮
 (required)                                          
 optimizer:adam                                  
 optimizer:sgd                                   
╰─────────────────────────────────────────────────────╯

subcommand(prefix_name=False): drop the prefix for one subcommand

tyro.conf.subcommand(prefix_name=False) is applied to a type within a union. It drops the parent prefix from that specific subcommand’s name.

Here, Adam opts out of the optimizer: prefix. SGD is unaffected:

@dataclasses.dataclass
class Adam:
    learning_rate: float = 1e-3
    beta1: float = 0.9


@dataclasses.dataclass
class SGD:
    learning_rate: float = 1e-2
    momentum: float = 0.9


@dataclasses.dataclass
class Config:
    optimizer: Union[
        Annotated[Adam, tyro.conf.subcommand(prefix_name=False)],
        SGD,
    ]


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] {adam,optimizer:sgd}

╭─ options ──────────────────────────────────────────╮
 -h, --help         show this help message and exit 
╰────────────────────────────────────────────────────╯
╭─ subcommands ──────────────────────────────────────╮
 (required)                                         
 adam                                           
 optimizer:sgd                                  
╰────────────────────────────────────────────────────╯

name="": suppress the field name from all subcommand names

tyro.conf.arg(name="") on the union field drops the field name from all subcommand names at once:

@dataclasses.dataclass
class Adam:
    learning_rate: float = 1e-3
    beta1: float = 0.9


@dataclasses.dataclass
class SGD:
    learning_rate: float = 1e-2
    momentum: float = 0.9


@dataclasses.dataclass
class Config:
    optimizer: Annotated[Union[Adam, SGD], tyro.conf.arg(name="")]


tyro.cli(Config, args=["--help"], prog="script.py")
usage: script.py [-h] {adam,sgd}

╭─ options ───────────────────────────────────╮
 -h, --help  show this help message and exit 
╰─────────────────────────────────────────────╯
╭─ subcommands ───────────────────────────────╮
 (required)                                  
 adam                                    
 sgd                                     
╰─────────────────────────────────────────────╯