Core Types & Functions#

The Fiddle configuration system builds on a core data model and functions.

Config#

class fiddle.Config(fn_or_cls, /, *args, **kwargs)[source]#

A mutable representation of a function or class’s parameters.

This class represents the configuration for a given function or class, exposing configured parameters as mutable attributes. For example, given a class:

class SampleClass:
  '''Example class for demonstration purposes.'''

  def __init__(self, arg, kwarg=None):
    self.arg = arg
    self.kwarg = kwarg

a configuration may (for instance) be accomplished via:

class_config = Config(SampleClass, 1, kwarg='kwarg')

or via:

class_config = Config(SampleClass)
class_config.arg = 1
class_config.kwarg = 'kwarg'

A function can be configured in the same ways:

def test_function(arg, kwarg=None):
  return arg, kwarg

fn_config = Config(test_function, 1)
fn_config.kwarg = 'kwarg'

If the class/function has positional arguments, they can be accessed through the [] syntax:

def test_function(a, b, /, c, *args):
  return locals()

fn_config = Config(test_function, 1, 2, 3, 4, 5)

# Read
assert fn_config[0] == 1
assert fn_config[:] == [1, 2, 3, 4, 5]

# Modify
fn_config[0] = 'a'
fn_config.c = 'c'

# `fdl.VARARGS` represents the start of variadic positional args (*args)
fn_config[fdl.VARARGS:] = ['x', 'y']
assert fn_config[:] == [1, 2, 3, 'x', 'y']

# Delete
del fn_config[0]
del fn_config[fdl.VARARGS:]
assert fn_config[:] == [fdl.NO_VALUE, 2, 3]

NOTE: Directly calling list methods like append and extend is not supported, and will not mutate the config. Like with Python lists, slice operations on Configs effectively create a copy of the underlying sequence.

NOTE: If using slice as key for modifying the config, and the slice spans over positional-only or positional-or-keyword arguments, the provided value must have the same length as that of the slice range.

fn_config[2:4] = [‘a’, ‘b’] # OK fn_config[2:4] = [‘m’] # Not OK. Will raise an error!

A Config instance may be transformed into instances and function outputs by passing it to the build function. The build function invokes each function or class in the configuration tree (appropriately propagating the built outputs from nested Config’s). For example, using the SampleClass config from above:

instance = build(class_config)
assert instance.arg == 1
assert instance.kwarg == 'kwarg'

If the same Config instance is used in multiple places within the configuration tree, its function or class is invoked only once during build, and the result shared across all occurrences of the Config instance. (See build documentation for further details.) To create a new instance of a Config with the same parameter settings that will yield a separate instance during build, copy.copy() or copy.deepcopy() may be used.

Partial#

class fiddle.Partial(fn_or_cls, /, *args, **kwargs)[source]#

A Partial config creates a partial function or class when built.

In some cases, it may be desired to leave a function or class uninvoked, and instead output a corresponding functools.partial object. Where the Config base class calls its underlying __fn_or_cls__ when built, this Partial instead results in a partially bound function or class:

partial_config = Partial(SampleClass)
partial_config.arg = 1
partial_config.kwarg = 'kwarg'
partial_class = build(partial_config)
instance = partial_class(arg=2)  # Keyword arguments can be overridden.
assert instance.arg == 2
assert instance.kwarg == 'kwarg'

A Partial can also be created from an existing Config, by using the fdl.cast() function. This results in a shallow copy that is decoupled from the Config used to create it. In the example below, any further changes to partial_config are not reflected by class_config (and vice versa):

partial_config = fdl.cast(Partial, class_config)
class_config.arg = 'new value'  # Further modification to `class_config`.
partial_class = build(partial_config)
instance = partial_class()
assert instance.arg == 'arg'  # The instance config is still 'arg'.

Partial also supports configuring function/class with positional arguments. Please see docstring of Config class for details.

build#

fiddle.build(buildable)[source]#

Builds buildable, recursively building nested Buildable instances.

This is the core function for turning a Buildable into a usable object. It recursively walks through buildable’s parameters, building any nested Config instances. Depending on the specific Buildable type passed (Config or Partial), the result is either the result of calling config.__fn_or_cls__ with the configured parameters, or a partial function or class with those parameters bound.

If the same Buildable instance is seen multiple times during traversal of the configuration tree, build is called only once (for the first instance encountered), and the result is reused for subsequent copies of the instance. This is achieved via the memo dictionary (similar to deepcopy). This has the effect that for configured class instances, each separate config instance is in one-to-one correspondence with an actual instance of the configured class after calling build (shared config instances <=> shared class instances).

Parameters:

buildable – A Buildable instance to build, or a nested structure of Buildable objects.

Returns:

The built version of buildable.


Buildable Manipulation Functions#

fiddle.get_callable(buildable)[source]#

Returns the callable of a Buildable.

Return type:

Union[Callable[..., TypeVar(T)], Type[TypeVar(T)]]

fiddle.update_callable(buildable, new_callable, drop_invalid_args=False)[source]#

Updates config to build new_callable instead.

When extending a base configuration, it can often be useful to swap one class for another. For example, an experiment may want to swap in a subclass that has augmented functionality.

update_callable updates config in-place (preserving argument history).

Parameters:
  • buildable (Buildable) – A Buildable (e.g. a fdl.Config) to mutate.

  • new_callable (Union[Callable[..., TypeVar(T)], Type[TypeVar(T)]]) – The new callable config should call when built.

  • drop_invalid_args (bool) – If True, arguments that don’t exist in the new callable will be removed from buildable. If False, raise an exception for such arguments.

Raises:

TypeError – if new_callable has varargs, or if there are arguments set on config that are invalid to pass to new_callable.

Return type:

None

fiddle.assign(buildable, /, **kwargs)[source]#

Assigns multiple arguments to buildable.

Although this function does not enable a caller to do something they can’t already do with other syntax, this helper function can be useful when manipulating deeply nested configs. Example:

cfg = # ...
fdl.assign(cfg.my.deeply.nested.child.object, arg_a=1, arg_b='b')

The above code snippet is equivalent to:

cfg = # ...
cfg.my.deeply.nested.child.object.arg_a = 1
cfg.my.deeply.nested.child.object.arg_b = 'b'
Parameters:
  • buildable (Buildable) – A Buildable (e.g. a fdl.Config) to set values upon.

  • **kwargs – The arguments and values to assign.

fiddle.copy_with(buildable, **kwargs)[source]#

Returns a shallow copy of buildable with updates to arguments.

Parameters:
  • buildable (TypeVar(BuildableT, bound= Buildable)) – A Buildable (e.g. a fdl.Config) to copy and mutate.

  • **kwargs – The arguments and values to assign.

Return type:

TypeVar(BuildableT, bound= Buildable)

fiddle.deepcopy_with(buildable, **kwargs)[source]#

Returns a deep copy of buildable with updates to arguments.

Note: if any Config’s inside buildable are shared with Config’s outside of buildable, then they will no longer be shared in the returned value. E.g., if cfg1.x.y is cfg2, then fdl.deepcopy_with(cfg1, ...).x.y is cfg2 will be False.

Parameters:
  • buildable (Buildable) – A Buildable (e.g. a fdl.Config) to copy and mutate.

  • **kwargs – The arguments and values to assign.

fiddle.cast(new_type, buildable)[source]#

Returns a copy of buildable that has been converted to new_type.

Requires that type(buildable) and type(new_type) be compatible. If the types may not be compatible, a warning will be issued, but the conversion will be attempted.

Parameters:
  • new_type (Type[TypeVar(BuildableT, bound= Buildable)]) – The type to convert to.

  • buildable (Buildable) – The config.Buildable that should be copied and converted.

Return type:

TypeVar(BuildableT, bound= Buildable)


ArgFactory#

class fiddle.ArgFactory(fn_or_cls, /, *args, **kwargs)[source]#

A configuration that creates an argument factory when built.

When an ArgFactory is used as a parameter for a fdl.Partial, the partial function built from that fdl.Partial will construct a new value for the parameter each time it is called. For example:

>>> def f(x, noise): return x + noise
>>> cfg = fdl.Partial(f, noise=fdl.ArgFactory(random.random))
>>> p = fdl.build(cfg)
>>> p(5) == p(5)  # noise has a different value for each call to `p`.
False

In contrast, if we replaced fdl.ArgFactory with fdl.Config in the above example, then the same noise value would be added each time p is called, since random.random would be called when fdl.build(cfg) is called.

ArgFactory’s can also be nested inside containers that are parameter values for a Partial. In this case, the partial function will construct the parameter value by copying the containers and replacing any ArgFactory with the result of calling its factory. Only the containers that (directly or indirectly) contain ArgFactory’s are copied; any elements of the containers that do not contain ArgFactory’s are not copied.

ArgFactory can also be used as the parameter for another ArgFactory, in which case a new value will be constructed for the child argument each time the parent argument is created.

ArgFactory should not be used as a top-level configuration object, or as the argument to a fdl.Config.


Advanced Functionality#

Buildable#

class fiddle.Buildable(fn_or_cls, /, *args, **kwargs)[source]#

Base class for buildable types (Config and Partial).

Buildable types implement a __build__ method that is called during fdl.build() with arguments set on the Buildable to get output for the corresponding instance.

Arguments are stored in the __arguments__ dict with the following “canonical storage format”: Positional-only and variadic-positional arguments use the position index of the argument as a key (of type int); all other arguments use the name of the argument as a key (of type str).

abstractmethod __build__(*args, **kwargs)[source]#

Builds output for this instance; see subclasses for details.

__copy__()[source]#

Shallowly copies this Buildable instance.

This copy implementation ensures that setting parameters on a copy of a Buildable won’t affect the original instance. However, the copy is shallow, so parameter values will still refer to the same instances of objects after the copy.

Returns:

A shallow copy of this Buildable.

__deepcopy__(memo)[source]#

Deepcopies this Buildable, skipping copying of __signature_info__.

__delattr__(name)[source]#

Unsets parameter name.

__dir__()[source]#

Provide a useful list of attribute names, optimized for Jupyter/Colab.

__dir__ is often implicitly called by tooling such as Jupyter/Colab to provide autocomplete suggestions. This implementation of __dir__ makes it easy to see what are valid attributes to get, set, or delete.

Return type:

Collection[str]

Returns:

A list of useful attribute names corresponding to set or unset parameters.

__eq__(other)[source]#

Returns true iff self and other contain the same argument values.

This compares the specific types of self and other, the function or class being configured, and then checks for equality in the configured arguments.

Default argument values are considered in this comparison: If one Buildable has an argument explicitly set to its default value while another does not, the two will still be considered equal (motivated by the fact that calls to the function or class being configured will be the same).

Argument history is not compared (i.e., it doesn’t matter how the Buildable’s being compared reached their current state).

Parameters:

other – The other value to compare self to.

Returns:

True if self equals other, False if not.

__getstate__()[source]#

Gets pickle serialization state, removing some fields.

For now, we discard the __signature_info__.signature (which can be recalculated), because these tend to contain values which cannot be serialized.

Returns:

Dict of serialized state.

__init__(fn_or_cls, /, *args, **kwargs)[source]#

Initialize for fn_or_cls, optionally specifying parameters.

Parameters:
  • fn_or_cls (Union[Buildable[TypeVar(T)], Callable[..., TypeVar(T)], Type[TypeVar(T)]]) – A callable to configure. The signature of this callable will determine the parameters this Buidlable can be used to configure.

  • *args – Any positional arguments to configure for fn_or_cls.

  • **kwargs – Any keyword arguments to configure for fn_or_cls.

__repr__()[source]#

Return repr(self).

__setattr__(name, value)[source]#

Sets parameter name to value.

__setstate__(state)[source]#

Loads pickle serialization state.

This re-derives the signature if not present.

Parameters:

state – State dictionary, from __getstate__.

Return type:

None