Source code for gem.utils
import collections
import functools
import numbers
from functools import cached_property # noqa: F401
from typing import Any
import numpy as np
[docs]
def groupby(iterable, key=None):
"""Groups objects by their keys.
:arg iterable: an iterable
:arg key: key function
:returns: list of (group key, list of group members) pairs
"""
if key is None:
key = lambda x: x
groups = collections.OrderedDict()
for elem in iterable:
groups.setdefault(key(elem), []).append(elem)
return groups.items()
[docs]
def make_proxy_class(name, cls):
"""Constructs a proxy class for a given class.
:arg name: name of the new proxy class
:arg cls: the wrapee class to create a proxy for
"""
def __init__(self, wrapee):
self._wrapee = wrapee
def make_proxy_property(name):
def getter(self):
return getattr(self._wrapee, name)
return property(getter)
dct = {'__init__': __init__}
for attr in dir(cls):
if not attr.startswith('_'):
dct[attr] = make_proxy_property(attr)
return type(name, (), dct)
# Implementation of dynamically scoped variables in Python.
[docs]
class UnsetVariableError(LookupError):
pass
_unset = object()
[docs]
class DynamicallyScoped(object):
"""A dynamically scoped variable."""
def __init__(self, default_value=_unset):
if default_value is _unset:
self._head = None
else:
self._head = (default_value, None)
[docs]
def let(self, value):
return _LetBlock(self, value)
@property
def value(self):
if self._head is None:
raise UnsetVariableError("Dynamically scoped variable not set.")
result, tail = self._head
return result
class _LetBlock(object):
"""Context manager representing a dynamic scope."""
def __init__(self, variable, value):
self.variable = variable
self.value = value
self.state = None
def __enter__(self):
assert self.state is None
value = self.value
tail = self.variable._head
scope = (value, tail)
self.variable._head = scope
self.state = scope
def __exit__(self, exc_type, exc_value, traceback):
variable = self.variable
assert self.state is variable._head
value, variable._head = variable._head
self.state = None
[docs]
@functools.singledispatch
def safe_repr(obj: Any) -> str:
"""Return a 'safe' repr for an object, accounting for floating point error.
Parameters
----------
obj :
The object to produce a repr for.
Returns
-------
str :
A repr for the object.
"""
raise TypeError(f"Cannot provide a safe repr for {type(obj).__name__}")
@safe_repr.register(str)
def _(text: str) -> str:
return text
@safe_repr.register(numbers.Integral)
def _(num: numbers.Integral) -> str:
return repr(num)
@safe_repr.register(numbers.Real)
def _(num: numbers.Real) -> str:
# set roundoff to close-to-but-not-exactly machine epsilon
precision = np.finfo(num).precision - 2
return "{:.{prec}}".format(num, prec=precision)
@safe_repr.register(np.ndarray)
def _(array: np.ndarray) -> str:
return f"{type(array).__name__}([{', '.join(map(safe_repr, array))}])"
@safe_repr.register(list)
def _(list_: list) -> str:
return f"[{', '.join(map(safe_repr, list_))}]"
@safe_repr.register(tuple)
def _(tuple_: tuple) -> str:
return f"({', '.join(map(safe_repr, tuple_))})"