# gin-config: Python configuration by way of dependency injection

> Google's gin-config rewrites the default parameter values of a Python function from a .gin file, so a training script becomes configurable without config objects, protos or factory boilerplate.

**google/gin-config** — Gin provides a lightweight configuration framework for Python

- Repository: https://github.com/google/gin-config
- Stars: 2,156 · Forks: 122
- Language: Python
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/google-gin-config

## Decorating a function so its defaults come from a file

The whole idea fits in one decorator. Mark a function with `@gin.configurable` and every one of its parameters becomes addressable from a config file, even though the function itself never mentions a config object. The decorator registers the callable with Gin and records its signature:

```python
@gin.configurable
def dnn(inputs,
        num_outputs,
        layer_sizes=(512, 512),
        activation_fn=tf.nn.relu):
  ...
```

That reads like an ordinary Keras helper, and it stays callable like one. A caller who passes `layer_sizes` explicitly still wins, because Gin's job is to supply default values, not to take arguments away. What changes is that a caller who passes nothing now has a value chosen by a `.gin` file rather than by whoever wrote the signature.

The binding itself is one line of assignment in a text file, using the syntax `function_name.parameter_name = value`:

```python
# Inside "config.gin"
dnn.layer_sizes = (1024, 512, 128)
```

Every Python literal works on the right side, including numbers, strings, lists, tuples and dicts. Classes behave the same way: decorating a class makes its constructor parameters configurable, and the class name rather than the function name is what you bind against.

```python
@gin.configurable
class DNN(object):
  # Constructor parameters become configurable.
  def __init__(self,
               num_outputs,
               layer_sizes=(512, 512),
               activation_fn=tf.nn.relu):
    ...
```

The cost of this indirection is real and worth naming early. The value that ends up in a parameter can live three files away from the code that reads it, and grep stops being a reliable way to find where a number came from. For a personal experiment script that tradeoff is usually worth it. For a service with a dozen engineers editing it, it is a genuine cost, and the countermeasure is a naming convention for config files rather than anything Gin provides.

## Configurable references for passing functions and built objects

Literals cover only half the parameters anyone actually wants to sweep. Switching a ReLU for a tanh is a function swap, and Gin handles that with a syntax it calls configurable references, written as `@name`:

```python
# Inside "config.gin"
dnn.activation_fn = @tf.nn.tanh
```

For that to work, `tf.nn.tanh` has to be registered with Gin, which is the subject of a separate section of the user guide on making existing classes or functions configurable. This is the part of the design that makes Gin more than a defaults file: the config language can name code, not just data, which is what lets a single experiment file switch optimizers or activation functions without editing Python.

When a parameter expects an object rather than a callable, evaluated references add parentheses to the syntax:

```python
# Inside "config.gin"
build_model.network_fn = @DNN()
```

Two rules come with this form. Every parameter of the referenced function or class must be supplied through Gin, because Gin has to know how to construct the object. And the result is not cached, so a fresh `DNN` instance is built on every call to `build_model`. For stateless layers that is harmless. For anything holding state or a large allocation, it turns a config binding into a per-call construction cost, and the README is explicit that no caching happens.

The reference syntax also lets you swap optimizer classes the same way, binding a class name where a training loop expects a factory:

```python
# Inside "config.gin"
train_fn.optimizer_cls = @tf.train.GradientDescentOptimizer
```

Note the import path used here. Bindings reference registered names, so the module path in the config has to match how the object was registered, which is why the README pairs every reference example with a note about registering external functions first.

## Scopes that let one function serve a generator and a discriminator

The obvious failure mode of a defaults-based system is wanting the same callable configured two different ways in the same run. The README walks through the GAN case: one `dnn` helper builds both the generator and the discriminator, and the two want different layer widths and different output dimensions. Gin answers this with scopes, where a name placed before a slash creates a separate set of bindings:

```python
# Inside "config.gin"
build_model.generator_network_fn = @generator/dnn
build_model.discriminator_network_fn = @discriminator/dnn

generator/dnn.layer_sizes = (128, 256)
generator/dnn.num_outputs = 784

discriminator/dnn.layer_sizes = (512, 256)
discriminator/dnn.num_outputs = 1

dnn.activation_fn = @tf.nn.tanh
```

The scope works in both directions: you can point a parameter at a scoped reference and you can bind values inside a scope, and an unscoped binding still applies to every scope of that function unless a scoped binding overrides it. In the example above `activation_fn` is set once at the top level and applies to both networks, which is the behavior most experiment files want.

What scopes buy is the ability to keep one implementation and vary the hyperparameters per role. What they cost is that a plain function name now has several meanings, so a reader tracing a config has to check whether a binding was scoped before concluding it applies. The slash syntax is compact enough to stay readable in a twenty-line config file and genuinely painful in a two-hundred-line one.

## Installing gin-config and binding the first config file

Installation from a package index is one command:

```bash
pip install gin-config
```

Installing from a source checkout takes three, and the second command is where the repository lands on disk:

```bash
git clone https://github.com/google/gin-config
cd gin-config
python -m setup.py install
```

The import depends on which framework you plan to use. Plain parameter binding needs only the base module:

```python
import gin
```

TensorFlow support arrives through a separate module, and Gin keeps it separate so that a project without TensorFlow does not pay for it:

```python
import gin.tf
```

A PyTorch module exists too, named `gin.torch`, which is the clearest hint that Gin was not designed around one framework. Once every configurable class and function has been defined or imported, a single call binds the file:

```python
gin.parse_config_file('config.gin')
```

For an experiment directory holding several config files plus ad hoc command line overrides, the README points at `gin.parse_config_files_and_bindings` in the user guide rather than demonstrating it inline. That function is the one to reach for in practice, since a sweep that writes one file per trial is the normal shape of this work, and calling `parse_config_file` repeatedly is not the same thing.

## Where the README stops and the user guide takes over

The README is a long tutorial that covers setup, bindings, references and scopes in depth, and it is genuinely good at the first four. It then points at `docs/index.md` for everything past that, including registering external functions, multi-file experiments and command line bindings. The tree confirms how little ships as code: `gin/` is the package, `tests/` the test suite, `setup.py` the installer, and there are `run_tests.sh` and `pip_pkg.sh` scripts for development and packaging.

The version story is the part worth a second look, and it is genuinely ambiguous. `setup.py` declares `_VERSION = '0.5.0'`, while the repository's GitHub releases list contains a single entry: `v0.1-alpha`, tagged Initial alpha release on 2018-07-12, with an empty body. So the installed version you get from pip and the tag history you see on the releases page do not line up, and pinning a released version is less straightforward here than the release list suggests. Apache 2.0 covers the code, and the README states plainly that this is not an official Google product.

On maintenance, the last push was on 2026-09-09. That is a recent commit against a library whose README tutorial is older than its commit history, which reads like a project receiving occasional fixes rather than one developing in public. Worth checking the commit log before you depend on it, since a quiet library that breaks against a new TensorFlow release is a real cost.

## How Gin compares with Hydra and plain dataclasses

The nearest alternative is not another config library but plain Python: a dataclass with defaults, passed down by a caller. For a small program that is less machinery, no config language to learn, and type checkers understand it. What it does not give you is the ability to change a value without touching the process, or to name a function as a value in a text file. If your configuration changes only when the code is redeployed, defaults in signatures are probably enough.

Hydra is the serious comparison, and the `Gin config vs Hydra` phrasing comes up often enough to be worth answering directly. Hydra composes configuration from files and directories, supports overriding from the command line, and is built around a schema rather than around function signatures. Its unit of composition is a config group; Gin's unit is a binding to a specific callable's parameter. Hydra is the better fit when your settings are structured data that several subsystems read, and it carries YAML as a dependency. Gin is the better fit when the thing you want to vary is a default argument deep inside a call chain, because Gin reaches that argument without any code passing a config object through every layer on the way.

Both share the failure mode that matters most: a value set far from the code that uses it. Neither removes the discipline of keeping experiment configs next to the runs that produced them, and neither gives you a record of which config produced which result unless you build that yourself.

## Conclusion

gin-config earns its place in a codebase where the same function is called from many places with different hyperparameters, which is the normal shape of a machine learning experiment. What it replaces is a configuration object, and what it adds is a binding language you have to learn before you can read someone else's experiment config. Verify three things before adopting it: that your parameters are reachable from decorated functions rather than buried in a builder object, that the team will tolerate scopes in config files, and that pip can resolve `gin-config` in the same environment as your framework. Start by decorating one function, binding one value, and reading `gin.parse_config_file` in the user guide under `docs/` before rewriting a training pipeline around it.

## FAQ

### What is gin-config and what problem does it solve?

gin-config is a Python configuration framework built on dependency injection. You decorate a function or class with `@gin.configurable` and a `.gin` file can then supply or change the default values of its parameters, which removes the need to write and thread through configuration objects and factory code.

### How do I install and use gin-config?

Run `pip install gin-config`, import `gin` (plus `gin.tf` or `gin.torch` for framework support), decorate the callables you want configurable, and call `gin.parse_config_file('config.gin')` once everything is defined. Use `gin.parse_config_files_and_bindings` when you want several files plus command line overrides.

### What is the difference between a binding and a configurable reference?

A binding assigns a value to a parameter, using `function_name.parameter_name = value` with any Python literal. A configurable reference uses the `@name` form to pass another registered function or class, and the `@name()` form to pass a freshly constructed instance. An instance is rebuilt on every call rather than cached.

### When is gin-config the wrong tool for a Python project?

It is a poor fit when configuration only changes on redeploy, since plain dataclass defaults need no extra machinery. It also fits awkwardly around code that builds objects inside long builder chains, because Gin can only configure parameters of functions and classes you have decorated. The scope syntax is the other warning sign: fine in a twenty-line config, hard to follow in a large one.

## Sources

- [google/gin-config on GitHub](https://github.com/google/gin-config)
- [Issues](https://github.com/google/gin-config/issues)
- [License: Apache-2.0](https://github.com/google/gin-config/blob/master/LICENSE)
- [README](https://github.com/google/gin-config/blob/master/README.md)
- [Releases](https://github.com/google/gin-config/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/google-gin-config
