Skip to content

Reference

scope(*args, **kwargs)

Bases: _HyperParameter

A thread-safe hyperparameter context scope

Examples:

create new scope

>>> ps = scope(a="a", b="b")           # create from call arguments
>>> ps = scope(**{"a": "a", "b": "b"}) # create from a dict

read parameters from scope

>>> ps.a() # read parameter
'a'
>>> ps.c("c")  # read parameter with default value if missing
'c'
>>> ps.c | "c" # another way for reading missing parameters
'c'

scope as a context scope

>>> with scope(**{"a": "a"}) as ps:
...     print(ps.a())
a

read parameter from scope in a function

>>> def foo():
...    with scope() as ps:
...        return ps.a()
>>> with scope(**{"a": "a", "b": "b"}) as ps:
...     foo() # foo should get scope using a with statement
'a'

modify parameters in nested scopes

>>> with scope.empty(**{'a': 1, 'b': 2}) as ps:
...     _repr_dict(ps.storage().storage())
...     with scope(**{'b': 3}) as ps:
...         _repr_dict(ps.storage().storage())
...     with scope() as ps:
...         _repr_dict(ps.storage().storage())
[('a', 1), ('b', 2)]
[('a', 1), ('b', 3)]
[('a', 1), ('b', 2)]

use object-style parameter key in scope

>>> with scope(**{"a.b.c": [1,2]}) as ps:
...     ps.a.b.c()
[1, 2]

access parameter with scope

>>> with scope(x=1):
...     scope.x(2) # read parameter
...     scope.y(2) # read a missing parameter with default value
...     scope.y | 2
...     scope.z = 3
...     scope.z | 0
1
2
2
3

convert scope to dict:

>>> ps = scope.empty(a=1, b=2)
>>> _repr_dict(dict(ps))
[('a', 1), ('b', 2)]
Source code in hyperparameter/api.py
def __init__(self, *args: str, **kwargs: Any) -> None:
    super().__init__()
    self.update(kwargs)
    for line in args:
        if "=" in line:
            k, v = line.split("=", 1)
            self.put(k, v)

__enter__()

enter a scope context

Examples:

>>> with scope():
...     scope.p = "origin"
...     with scope(**{"p": "origin"}) as ps:
...         ps.storage().storage()      # outer scope
...         with scope() as ps:   # unmodified scope
...             ps.storage().storage()  # inner scope
...         with scope(**{"p": "modified"}) as ps: # modified scope
...             ps.storage().storage()  # inner scope with modified params
...         _ = scope(**{"p": "modified"}) # not used in with ctx
...         with scope() as ps:   # unmodified scope
...             ps.storage().storage()  # inner scope
{'p': 'origin'}
{'p': 'origin'}
{'p': 'modified'}
{'p': 'origin'}
Source code in hyperparameter/api.py
def __enter__(self) -> "scope":
    """enter a `scope` context

    Examples
    --------
    >>> with scope():
    ...     scope.p = "origin"
    ...     with scope(**{"p": "origin"}) as ps:
    ...         ps.storage().storage()      # outer scope
    ...         with scope() as ps:   # unmodified scope
    ...             ps.storage().storage()  # inner scope
    ...         with scope(**{"p": "modified"}) as ps: # modified scope
    ...             ps.storage().storage()  # inner scope with modified params
    ...         _ = scope(**{"p": "modified"}) # not used in with ctx
    ...         with scope() as ps:   # unmodified scope
    ...             ps.storage().storage()  # inner scope
    {'p': 'origin'}
    {'p': 'origin'}
    {'p': 'modified'}
    {'p': 'origin'}
    """

    self._storage.enter()
    return self

current() staticmethod

get current scope

Examples:

>>> with scope(a=1) as ps:
...     scope.current().a("empty") # read `a` from current `scope`
'1'
>>> with scope() as ps1:
...     with scope(a=1) as ps2:
...         scope.current().a = 2  # set parameter `a` = 2
...         scope.a("empty")       # read `a` in `ps2`
...     scope.a("empty")           # read `a` in `ps1`, where `a` is not set
'2'
'empty'
Source code in hyperparameter/api.py
@staticmethod
def current() -> "scope":
    """get current `scope`

    Examples
    --------
    >>> with scope(a=1) as ps:
    ...     scope.current().a("empty") # read `a` from current `scope`
    '1'

    >>> with scope() as ps1:
    ...     with scope(a=1) as ps2:
    ...         scope.current().a = 2  # set parameter `a` = 2
    ...         scope.a("empty")       # read `a` in `ps2`
    ...     scope.a("empty")           # read `a` in `ps1`, where `a` is not set
    '2'
    'empty'
    """
    retval = scope()
    retval._storage = TLSKVStorage.current()
    return retval

empty(*args, **kwargs) staticmethod

create an empty scope.

Examples:

>>> with scope(a="not empty") as ps: # start a new scope `a` = 'not empty'
...     scope.a("empty")             # read parameter `a`
...     with scope.empty() as ps2:   # parameter `a` is cleared in ps2
...         scope.a("empty")         # read parameter `a` = 'empty'
'not empty'
'empty'
Source code in hyperparameter/api.py
@staticmethod
def empty(*args: str, **kwargs: Any) -> "scope":
    """create an empty `scope`.

    Examples
    --------
    >>> with scope(a="not empty") as ps: # start a new scope `a` = 'not empty'
    ...     scope.a("empty")             # read parameter `a`
    ...     with scope.empty() as ps2:   # parameter `a` is cleared in ps2
    ...         scope.a("empty")         # read parameter `a` = 'empty'
    'not empty'
    'empty'
    """
    retval = scope().clear().update(kwargs)
    for line in args:
        if "=" in line:
            k, v = line.split("=", 1)
            retval.put(k, v)
    return retval

init(params=None) staticmethod

init scope for a new thread.

Source code in hyperparameter/api.py
@staticmethod
def init(params: Optional[Dict[str, Any]] = None) -> None:
    """init scope for a new thread."""
    if params is None:
        params = {}
    scope(**params).__enter__()

param(name_or_func)

param(func: Callable) -> Callable
param(name: str) -> Callable[[Callable], Callable]

Convert keyword arguments into hyperparameters

Examples:

>>> @param
... def foo(a, b=2, c='c', d=None):
...     print(a, b, c, d)
>>> foo(1)
1 2 c None
>>> with scope('foo.b=3'):
...     foo(2)
2 3 c None

classes are also supported:

>>> @param
... class foo:
...     def __init__(self, a, b=2, c='c', d=None):
...         print(a, b, c, d)
>>> obj = foo(1)
1 2 c None
>>> with scope('foo.b=3'):
...     obj = foo(2)
2 3 c None
>>> @param('myns.foo.params')
... def foo(a, b=2, c='c', d=None):
...     print(a, b, c, d)
>>> foo(1)
1 2 c None
>>> with scope('myns.foo.params.b=3'):
...     foo(2)
2 3 c None
>>> with scope('myns.foo.params.b=3'):
...     scope.myns.foo.params.b = 4
...     foo(2)
2 4 c None
Source code in hyperparameter/api.py
def param(
    name_or_func: Union[str, Callable, None],
) -> Union[Callable, Callable[[Callable], Callable]]:
    """Convert keyword arguments into hyperparameters

    Examples
    --------

    >>> @param
    ... def foo(a, b=2, c='c', d=None):
    ...     print(a, b, c, d)

    >>> foo(1)
    1 2 c None

    >>> with scope('foo.b=3'):
    ...     foo(2)
    2 3 c None

    classes are also supported:
    >>> @param
    ... class foo:
    ...     def __init__(self, a, b=2, c='c', d=None):
    ...         print(a, b, c, d)

    >>> obj = foo(1)
    1 2 c None

    >>> with scope('foo.b=3'):
    ...     obj = foo(2)
    2 3 c None

    >>> @param('myns.foo.params')
    ... def foo(a, b=2, c='c', d=None):
    ...     print(a, b, c, d)
    >>> foo(1)
    1 2 c None

    >>> with scope('myns.foo.params.b=3'):
    ...     foo(2)
    2 3 c None

    >>> with scope('myns.foo.params.b=3'):
    ...     scope.myns.foo.params.b = 4
    ...     foo(2)
    2 4 c None
    """

    if callable(name_or_func):
        return param(None)(name_or_func)

    if has_rust_backend:

        def hashed_wrapper(func: Callable) -> Callable:
            predef_kws: Dict[str, int] = {}
            predef_defaults: Dict[str, Any] = {}

            if name_or_func is None:
                namespace = func.__name__
            else:
                namespace = name_or_func

            signature = inspect.signature(func)
            for k, v in signature.parameters.items():
                if v.default != v.empty:
                    name = "{}.{}".format(namespace, k)
                    predef_kws[k] = xxh64(name)
                    predef_defaults[k] = v.default

            @functools.wraps(func)
            def inner(*arg: Any, **kws: Any) -> Any:
                with scope() as hp:
                    for k, v in predef_kws.items():
                        if k not in kws:
                            try:
                                val = hp._storage.get_entry(v)
                                kws[k] = _coerce_with_default(val, predef_defaults[k])
                            except ValueError:
                                pass
                    return func(*arg, **kws)

            inner._auto_param_namespace = namespace  # type: ignore[attr-defined]
            inner._auto_param_wrapped = func  # type: ignore[attr-defined]
            return inner

        return hashed_wrapper

    def wrapper(func: Callable) -> Callable:
        predef_kws: Dict[str, str] = {}
        predef_val: Dict[str, Any] = {}

        if name_or_func is None:
            namespace = func.__name__
        else:
            namespace = name_or_func

        signature = inspect.signature(func)
        for k, v in signature.parameters.items():
            if v.default != v.empty:
                name = "{}.{}".format(namespace, k)
                predef_kws[k] = name
                predef_val[name] = v.default

        @functools.wraps(func)
        def inner(*arg: Any, **kws: Any) -> Any:
            with scope() as hp:
                local_params: Dict[str, Any] = {}
                for k, v in predef_kws.items():
                    if k not in kws:
                        val = getattr(hp(), v).get_or_else(predef_val[v])
                        kws[k] = val
                        local_params[v] = hp.get(v)
                    else:
                        local_params[v] = predef_val[v]
                return func(*arg, **kws)

        inner._auto_param_namespace = namespace  # type: ignore[attr-defined]
        inner._auto_param_wrapped = func  # type: ignore[attr-defined]
        return inner

    return wrapper