Hierarchical parsing¶
Hierarchical parsing allows complex class types to be expaded into individual parameters. For each property of a class type a parameter is generated with name being the path to the parameter. This is illustrated in the following example:
@dataclass
class Model:
'''
:param learning_rate: Learning rate
:param num_features: Number of features
'''
learning_rate: float = 1e-4
num_features: int = 5
@aclick.command
def train(model: Model, num_epochs: int):
print(f'''lr: {learning_rate},
num_features: {model.num_features},
num_epochs: {num_epochs}''')
The corresponding help page looks as follows:
$ python train.py --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model-learning-rate FLOAT Learning rate [default: 0.0001]
--model-num-features INTEGER Number of features [default: 5]
--num-epochs INTEGER [required]
Note, that in some cases hierarchical parsing may not support your type,
default value, etc. In particular, positional arguments cannot be expanded
in hierarchical parsing. If your type in not supported, you can either inline
it by wrapping it with aclick.default_from_str()
wrapper or turn of hierarchical
parsing by passing hierarchical = False
to the command constructor.
With a deep class structure the parameter names can grow long. Parameters
can be renamed to prevent long names. For more information see hierarchical parsing and FlattenParameterRenamer
.
Custom classes¶
User defined classes are naturally expand to individual parameters if hierarchical = True
(default).
We can have a class hierarchy and parameters will be expanded to the lowest level. This
can be seen in the following example:
class Schedule:
def __init__(self, type: str, constant: float = 1e-4):
self.type = type
self.constant = constant
@dataclass
class Model:
'''
:param learning_rate: Learning rate
:param num_features: Number of features
'''
learning_rate: Schedule
num_features: int = 5
@aclick.command
def train(model: Model, num_epochs: int):
pass
The corresponding help page looks as follows:
$ python train.py --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model-learning-rate-type TEXT
[required]
--model-learning-rate-constant FLOAT
[default: 0.0001]
--model-num-features INTEGER Number of features [default: 5]
--num-epochs INTEGER [required]
Optional values¶
If a property of a class type is optional, there will be a boolean flag with the property name indicating whether the class is actually present. This is illustrated in the following example:
class Schedule:
def __init__(self, type: str, constant: float = 1e-4):
self.type = type
self.constant = constant
@dataclass
class Model:
'''
:param learning_rate: Learning rate
:param num_features: Number of features
'''
learning_rate: t.Optional[Schedule] = None
num_features: int = 5
@aclick.command
def train(model: Model, num_epochs: int):
pass
The corresponding help page looks as follows:
$ python train.py --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model-learning-rate Set model.learning_rate to a schedule instance
--model-num-features INTEGER Number of features [default: 5]
--num-epochs INTEGER [required]
And after specifying that we want to instantiate the learning_rate
instance:
$ python train.py --model-learning-rate --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model-learning-rate Set model.learning_rate to a schedule instance
--model-learning-rate-type TEXT
[required]
--model-learning-rate-constant FLOAT
[default: 0.0001]
--model-num-features INTEGER Number of features [default: 5]
--num-epochs INTEGER [required]
Union of classes¶
We can also specify multiple types for a parameter or property and the concrete type will be specified when invoking the command. This scenario is illustrated in the following example:
@dataclass
class ModelA:
'''
:param learning_rate: Learning rate
:param num_features: Number of features
'''
learning_rate: float = 0.1
num_features: int = 5
@dataclass
class ModelB:
'''
:param learning_rate: Learning rate
:param num_layers: Number of layers
'''
learning_rate: float = 0.2
num_layers: int = 10
@aclick.command
def train(model: t.Union[ModelA, ModelB], num_epochs: int):
pass
The corresponding help page looks as follows:
$ python train.py --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model [model-a|model-b] Set model to a type instance [required]
--num-epochs INTEGER [required]
And after specifying that we want to use ModelB
class:
$ python train.py --model model-b --help
Usage: train.py [OPTIONS]
Options:
--help Show this message and exit.
--model [model-a|model-b] Set model to a type instance [required]
--model-learning-rate FLOAT Learning rate [default: 0.2]
--model-num-layers INTEGER Number of layers [default: 10]
--num-epochs INTEGER [required]