Instantiating objects with Hydra
One of the best ways to drive different behavior in an application is to instantiate different implementations of an interface. The code using the instantiated object only knows the interface which remains constant, but the behavior is determined by the actual object instance.
Hydra provides hydra.utils.instantiate() (and its alias hydra.utils.call()) for instantiating objects and calling functions. Prefer instantiate for creating objects and call for invoking functions.
Call/instantiate supports:
- Constructing an object by calling the
__init__method - Calling functions, static functions, class methods and other callable global objects
Top-level list and tuple inputs are instantiated element by element. Partial instantiation is not supported for these sequence inputs.
Instantiate API (Expand for details)
def instantiate(config: Any, *args: Any, **kwargs: Any) -> Any:
"""
:param config: An config object describing what to call and what params to use.
In addition to the parameters, the config must contain:
_target_ : target class or callable name (str)
And may contain:
_args_: List-like of positional arguments to pass to the target
_recursive_: Construct nested objects as well (bool).
True by default.
may be overridden via a _recursive_ key in
the kwargs
_convert_: Conversion strategy
none : Passed objects are DictConfig, ListConfig and
TupleConfig, default
partial : Passed objects are converted to dict, list and
tuple, with the exception of Structured Configs
(and their fields).
object : Passed objects are converted to dict, list and tuple.
Structured Configs are converted to instances of the
backing dataclass / attr class.
all : Passed objects are dicts, lists, tuples and
primitives without a trace of OmegaConf containers.
Structured configs are converted to primitive
containers too.
_partial_: If True, return functools.partial wrapped method or object
False by default. Configure per target.
:param _target_whitelist_: A target string, list of target strings,
object returned by target_whitelist(), or
UNSAFE_ALLOW_ALL_TARGETS. Passing None
preserves legacy behavior unless a
target_whitelist() context is active.
:param args: Optional positional parameters pass-through
:param kwargs: Optional named parameters to override
parameters in the config object. Parameters not present
in the config objects are being passed as is to the target.
IMPORTANT: dataclasses instances in kwargs are interpreted as config
and cannot be used as passthrough
:return: if _target_ is a class name: the instantiated object
if _target_ is a callable: the return value of the call
"""
# Alias for instantiate
call = instantiate
Target whitelist API (Expand for details)
def target_whitelist(
target_whitelist: str | Sequence[str] | UNSAFE_ALLOW_ALL_TARGETS | None,
reset: bool = False,
):
"""
Create a target whitelist object for hydra.utils.instantiate().
The returned object can be used as a context manager to apply a whitelist
to instantiate() calls in the current context, or passed to instantiate()
as _target_whitelist_.
:param target_whitelist: A target string, list of target strings, or
UNSAFE_ALLOW_ALL_TARGETS. A trailing .*
allows targets under a package prefix.
:param reset: If True, ignore any outer target_whitelist() context.
If False, add these targets to the current context.
"""
The config passed to these functions must have a key called _target_, with the value of a fully qualified class name, class method, static method or callable.
For convenience, None config results in a None object.
Create a whitelist object with target_whitelist(). Use it as a context manager
to apply a whitelist to every instantiate() call in a block. This is useful
when another function or framework calls instantiate() internally, or when
calling instantiate() multiple times with the same whitelist:
from hydra.utils import instantiate, target_whitelist
with target_whitelist("my_app.*"):
framework_function(cfg)
Or pass it directly to one instantiate() call:
model = instantiate(
cfg.model,
_target_whitelist_=target_whitelist("my_app.models.*"),
)
Nested target_whitelist() scopes stack by default: an inner scope adds its
targets to the outer scope.
with target_whitelist("my_app.models.*"):
with target_whitelist("my_app.optimizers.*"):
train(cfg)
Use reset=True when the inner scope should replace the outer scope instead of
adding to it:
with target_whitelist("my_app.*"):
with target_whitelist("my_app.models.*", reset=True):
instantiate(cfg.model)
For simple direct calls, _target_whitelist_ also accepts a string or list of
strings.
The wildcard * by itself is not allowed as a whitelist entry. To explicitly
preserve legacy all-target behavior, use UNSAFE_ALLOW_ALL_TARGETS:
from hydra.utils import UNSAFE_ALLOW_ALL_TARGETS, instantiate
component = instantiate(
cfg.component,
_target_whitelist_=UNSAFE_ALLOW_ALL_TARGETS,
)
Named arguments : Config fields (except reserved fields like _target_) are passed as named arguments to the target.
Named arguments in the config can be overridden by passing named argument with the same name in the instantiate() call-site.
Call-site arguments are applied separately from the input configuration. They replace the corresponding arguments passed to the target without modifying the input configuration. They therefore do not affect interpolation resolution or Structured Config coercion in the input configuration. Configuration values are resolved lazily as instantiation proceeds instead of resolving and copying the full configuration tree up front. This avoids processing unrelated configuration values and allows runtime state established by an earlier target to be used while resolving a later argument.
See the Hydra 1.4 upgrade guide for the compatibility impact and an example.
Positional arguments : The config may contain a _args_ field representing positional arguments to pass to the target.
The positional arguments can be overridden together by passing positional arguments in the instantiate() call-site.
Simple usage
Your application might have an Optimizer class:
class Optimizer:
algo: str
lr: float
def __init__(self, algo: str, lr: float) -> None:
self.algo = algo
self.lr = lr
optimizer:
_target_: my_app.Optimizer
algo: SGD
lr: 0.01
with target_whitelist("my_app.*"):
opt = instantiate(cfg.optimizer)
print(opt)
# Optimizer(algo=SGD,lr=0.01)
You can override parameters at the call-site:
with target_whitelist("my_app.*"):
opt = instantiate(
cfg.optimizer,
lr=0.2,
)
print(opt)
# Optimizer(algo=SGD,lr=0.2)
Recursive instantiation
Let's add a Dataset and a Trainer class. The trainer holds a Dataset and an Optimizer instances.
class Dataset:
name: str
path: str
def __init__(self, name: str, path: str) -> None:
self.name = name
self.path = path
class Trainer:
def __init__(self, optimizer: Optimizer, dataset: Dataset) -> None:
self.optimizer = optimizer
self.dataset = dataset
With the following config, you can instantiate the whole thing with a single call:
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 will instantiate nested objects recursively by default.
with target_whitelist("my_app.*"):
trainer = instantiate(cfg.trainer)
print(trainer)
# Trainer(
# optimizer=Optimizer(algo=SGD,lr=0.01),
# dataset=Dataset(name=Imagenet, path=/datasets/imagenet)
# )
You can override parameters for nested objects:
with target_whitelist("my_app.*"):
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)
# )
Similarly, positional arguments of nested objects can be overridden:
with target_whitelist("my_app.*"):
obj = instantiate(
cfg.object,
# pass 1 and 2 as positional arguments
# to the target object
1, 2,
# pass 3 and 4 as positional arguments
# to a nested child object
child={"_args_": [3, 4]},
)
Disable recursive instantiation
You can disable recursive instantiation by setting _recursive_ to False in the config node or in the call-site
In that case the Trainer object will receive an OmegaConf DictConfig for nested dataset and optimizer instead of the instantiated objects.
trainer = instantiate(
cfg.trainer,
_recursive_=False,
_target_whitelist_="my_app.Trainer",
)
print(trainer)
Output:
Trainer(
optimizer={
'_target_': 'my_app.Optimizer', 'algo': 'SGD', 'lr': 0.01
},
dataset={
'_target_': 'my_app.Dataset', 'name': 'Imagenet', 'path': '/datasets/imagenet'
}
)
Parameter conversion strategies
By default, the parameters passed to the target are either primitives (int,
float, bool etc) or OmegaConf containers (DictConfig, ListConfig,
TupleConfig). OmegaConf containers have many advantages over primitive dicts,
lists, and tuples,
including convenient attribute access for keys,
duck-typing as instances of dataclasses or attrs classes, and
support for variable interpolation
and custom resolvers.
If the callable targeted by instantiate leverages OmegaConf's features, it
will make sense to pass DictConfig, ListConfig, and TupleConfig instances
directly to that callable.
That being said, in many cases it's desired to pass normal Python dicts and
lists and tuples, rather than DictConfig, ListConfig, or TupleConfig
instances, as arguments to your callable. You can change instantiate's argument
conversion strategy using the _convert_ parameter. Supported values are:
"none": Default behavior, Use OmegaConf containers"partial": Convert OmegaConf containers to dict, list, and tuple, except Structured Configs, which remain as DictConfig instances."object": Convert OmegaConf containers to dict, list, and tuple, except Structured Configs, which are converted to instances of the backing dataclass / attr class usingOmegaConf.to_object."all": Convert everything to primitive containers
The conversion strategy applies recursively to all subconfigs of the instantiation target. Here is an example demonstrating the various conversion strategies:
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", _target_whitelist_="__main__.*")
assert isinstance(obj_none, MyTarget)
assert isinstance(obj_none.foo, DictConfig)
assert isinstance(obj_none.bar, DictConfig)
obj_partial = instantiate(cfg, _convert_="partial", _target_whitelist_="__main__.*")
assert isinstance(obj_partial, MyTarget)
assert isinstance(obj_partial.foo, DictConfig)
assert isinstance(obj_partial.bar, dict)
obj_object = instantiate(cfg, _convert_="object", _target_whitelist_="__main__.*")
assert isinstance(obj_object, MyTarget)
assert isinstance(obj_object.foo, Foo)
assert isinstance(obj_object.bar, dict)
obj_all = instantiate(cfg, _convert_="all", _target_whitelist_="__main__.*")
assert isinstance(obj_all, MyTarget)
assert isinstance(obj_all.foo, dict)
assert isinstance(obj_all.bar, dict)
Passing the _convert_ keyword argument to instantiate has the same effect as defining
a _convert_ attribute on your config object. Here is an example creating
instances of MyTarget that are equivalent to the above:
cfg_none = OmegaConf.create({..., "_convert_": "none"})
obj_none = instantiate(cfg_none, _target_whitelist_="__main__.*")
cfg_partial = OmegaConf.create({..., "_convert_": "partial"})
obj_partial = instantiate(cfg_partial, _target_whitelist_="__main__.*")
cfg_object = OmegaConf.create({..., "_convert_": "object"})
obj_object = instantiate(cfg_object, _target_whitelist_="__main__.*")
cfg_all = OmegaConf.create({..., "_convert_": "all"})
obj_all = instantiate(cfg_all, _target_whitelist_="__main__.*")
Partial Instantiation
Sometimes you may not set all parameters needed to instantiate an object from the configuration, in this case you can set
_partial_ to be True to get a functools.partial wrapped object or method, then complete initializing the object in
the application code. Here is an example:
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})"
model:
_target_: my_app.Model
optim_partial:
_partial_: true
_target_: my_app.Optimizer
algo: SGD
lr: 0.01
with target_whitelist("my_app.*"):
model = instantiate(cfg.model)
print(model)
# "Model(Optimizer=Optimizer(algo=SGD,lr=0.01),lr=0.01)
If you are repeatedly instantiating the same config,
using _partial_=True may provide a significant speedup as compared with regular (non-partial) instantiation.
factory = instantiate(
config,
_partial_=True,
_target_whitelist_="my_app.*",
)
obj = factory()
In the above example, repeatedly calling factory would be faster than repeatedly calling instantiate(config).
A caveat of this approach is that the same keyword arguments would be re-used in each call to 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,
_target_whitelist_="__main__.*",
)
bar1 = bar_factory()
bar2 = bar_factory()
assert bar1 is not bar2
assert bar1.foo is bar2.foo # the `Foo` instance is re-used here
This does not apply if _partial_=False,
in which case a new Foo instance would be created with each call to instantiate.
Instantiation of builtins
The value of _target_ passed to instantiate should be a "dotpath" pointing
to some callable that can be looked up via a combination of import and getattr.
If you want to target one of Python's built-in functions (such as len or print or divmod),
you will need to provide a dotpath looking up that function in Python's builtins module.
from hydra.utils import instantiate
# instantiate({"_target_": "len"}, [1,2,3]) # this gives an InstantiationException
instantiate(
{"_target_": "builtins.len"},
[1,2,3],
_target_whitelist_="builtins.len",
) # this works, returns the number 3
Dotpath lookup machinery
Hydra looks up a given _target_ by attempting to find a module that
corresponds to a prefix of the given dotpath and then looking for an object in
that module corresponding to the dotpath's tail. For example, to look up a _target_
given by the dotpath "my_module.my_nested_module.my_object", hydra first locates
the module my_module.my_nested_module, then find my_object inside that nested module.
Hydra exposes an API allowing direct use of this dotpath lookup machinery. The following three functions, which can be imported from the hydra.utils module, accept a string-typed dotpath as an argument and return the located class/callable/object:
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
"""
...