Instance factories are callables that return class instances. This is useful for dependency injection when a class requires parameters that are only available after injection (e.g., an optimizer that needs model parameters).
When instantiate() is called, a partial function is provided. You can define these factories using two approaches:
1. Using Callable
Use Callable[[ArgType1, ArgType2], ReturnType] in the type hint.
- Limitation: Only supports positional and unnamed parameters.
- Example:
type=Callable[[Iterable], Optimizer]
2. Using Protocol
Define a Protocol with a __call__ method. This is the preferred method if you need to support keyword arguments during the injection phase.
- Example:
class OptimizerFactory(Protocol):
def __call__(self, params: Iterable) -> Optimizer: ...
Default Values:
You can provide a default factory using a lambda in a class signature. Note that add_argument does not support AST resolving for lambdas; in that case, use a dictionary with class_path and init_args as the default value.
from typing import Callable, Iterable, Protocol
class Optimizer:
def __init__(self, params: Iterable):
self.params = params
class SGD(Optimizer):
def __init__(self, params: Iterable, lr: float):
super().__init__(params)
self.lr = lr
class OptimizerFactory(Protocol):
def __call__(self, params: Iterable) -> Optimizer: ...
parser = ArgumentParser()
# Using Protocol to allow keyword arguments like params=[1, 2]
parser.add_argument("--optimizer", type=OptimizerFactory)
value = {
"class_path": "__main__.SGD",
"init_args": {"lr": 0.02},
}
cfg = parser.parse_args(["--optimizer", str(value)])
init = parser.instantiate(cfg)
optimizer = init.optimizer(params=[6, 5])
print(optimizer.lr) # 0.02