In [0]:
from __future__ import division
from __future__ import print_function

import random

import gin


# When using Gin interactively, reregistering a function is not an error.
gin.enter_interactive_mode()

Defining "configurables"

Any function or class can be decorated with @gin.configurable, making it possible to provide or override default values for its parameters using Gin. Gin uses a simple function_name.parameter_name = value syntax to "bind" values to parameters.


In [0]:
gin.clear_config()

@gin.configurable
def say_hello(name="world"):
  print("Hello %s!" % name)

# Decorated functions or classes preserve their default behavior.
say_hello()

# Bindings are usually specified in a file, e.g., "config.gin". For simplicity
# here we pass a string directly to `gin.parse_config`.
gin.parse_config("""
say_hello.name = "Gin"
""")

# With the above config, the "name" parameter now defaults to "Gin".
say_hello()

# But the caller can always override it.
say_hello("world")


Hello world!
Hello Gin!
Hello world!

Any Python literal is an acceptable "value" in a Gin binding. Classes can be made configurable too.


In [0]:
gin.clear_config()

@gin.configurable
class Picker(object):
  # Bindings affect the constructor of the class.
  def __init__(self, items):
    self._items = items

  def pick(self):
    print(random.choice(self._items))

Picker(['item']).pick()

gin.parse_config("""
Picker.items = ['one', 2, ((), (), ()),]
""")

Picker().pick()


item
2

When calling a function where arguments are expected to be supplied by Gin, passing the value gin.REQUIRED clearly documents this expectation in the code.


In [0]:
gin.clear_config()

# Calling Picker with a missing argument yields an informative but somewhat
# verbose error message.
try:
  Picker()
except TypeError as e:
  print('Error message (missing arg):', e)

# We can use gin.REQUIRED to indicate we expect the value to be supplied by Gin.
# This improves the readability of the code and clarifies the error message.
try:
  Picker(gin.REQUIRED)
except RuntimeError as e:
  print('Error message (gin.REQUIRED):', e)

# gin.REQUIRED can also be used for keyword arguments.
try:
  say_hello(name=gin.REQUIRED)
except RuntimeError as e:
  print('Error message (gin.REQUIRED):', e)


Error message (missing arg): __init__() takes exactly 2 arguments (1 given)
  No values supplied by Gin or caller for arguments: ['items']
  Gin had values bound for: []
  Caller supplied values for: ['self']
  In call to configurable 'Picker' (<unbound method Picker.__init__>)
Error message (gin.REQUIRED): Required bindings for `Picker` not provided in config: ['items']
Error message (gin.REQUIRED): Required bindings for `say_hello` not provided in config: ['name']

Passing "references"

Gin allows "references" to registered functions or classes to be used as values, via the @fn_or_class syntax.


In [0]:
gin.clear_config()

@gin.configurable
def return_a_value():
  return 'fn1_return'

@gin.configurable
def print_arg(arg):
  print('arg = %r' % arg)

gin.parse_config("""
print_arg.arg = @return_a_value
""")
print_arg(gin.REQUIRED)


arg = <function return_a_value at 0xc2457d0>

References can be "evaluated", using the syntax @fn_or_class().


In [0]:
gin.parse_config("""
print_arg.arg = @return_a_value()
""")
print_arg(gin.REQUIRED)


arg = 'fn1_return'

Evaluated references are re-evaluated on every call.


In [0]:
@gin.configurable
def randint():
  return random.randint(0, 10)

gin.parse_config("""
print_arg.arg = @randint()
""")
print_arg(gin.REQUIRED)
print_arg(gin.REQUIRED)
print_arg(gin.REQUIRED)
print_arg(gin.REQUIRED)


arg = 3
arg = 4
arg = 1
arg = 3

Scopes (configuring the same function in multiple ways)

Bindings can be "scoped", using the scope/fn_or_class.parameter = value syntax. Such bindings are only in effect when the function is called with that scope active. References can be marked with a scope (@scope/fn_or_class) to force the associated function or class to be called within the specified scope.


In [0]:
@gin.configurable
def return_arg(arg):
  return arg

@gin.configurable
def call_fns(fn1, fn2):
  print('fn1() = %r' % fn1())
  print('fn2() = %r' % fn2())

gin.parse_config("""
call_fns.fn1 = @scope_a/return_arg
scope_a/return_arg.arg = 'scope_a'

call_fns.fn2 = @scope_b/return_arg
scope_b/return_arg.arg = 'scope_b'
""")

call_fns(gin.REQUIRED, gin.REQUIRED)


fn1() = 'scope_a'
fn2() = 'scope_b'