# types¶

Module with general purpose types.

nutils.types.aspreprocessor(apply)

Convert apply into a preprocessor decorator. When applied to a function, wrapped, the returned decorator preprocesses the arguments with apply before calling wrapped. The apply function should return a tuple of args (tuple or list) and kwargs (dict). The decorated function wrapped will be called with wrapped(*args, **kwargs). The apply function is allowed to change the signature of the decorated function.

Examples

The following preprocessor swaps two arguments.

>>> @aspreprocessor
... def swapargs(a, b):
...   return (b, a), {}


Decorating a function with swapargs will cause the arguments to be swapped before the wrapped function is called.

>>> @swapargs
... def func(a, b):
...   return a, b
>>> func(1, 2)
(2, 1)

nutils.types.apply_annotations(wrapped)

Decorator that applies annotations to arguments. All annotations should be callables taking one argument and returning a transformed argument. All annotations are strongly recommended to be idempotent.

If a parameter of the decorated function has a default value None and the value of this parameter is None as well, the annotation is omitted.

Examples

Consider the following function.

>>> @apply_annotations
... def f(a:tuple, b:int):
...   return a + (b,)


When calling f with a list and str as arguments, the apply_annotations() decorator first applies tuple and int to the arguments before passing them to the decorated function.

>>> f([1, 2], '3')
(1, 2, 3)


The following example illustrates the behavior of parameters with default value None.

>>> addone = lambda x: x+1
>>> @apply_annotations
...   return a


When calling g without arguments or with argument None, the annotation addone is not applied. Note that None + 1 would raise an exception.

>>> g() is None
True
>>> g(None) is None
True


When passing a different value, the annotation is applied:

>>> g(1)
2

nutils.types.argument_canonicalizer(signature)

Returns a function that converts arguments matching signature to canonical positional and keyword arguments. If possible, an argument is added to the list of positional arguments, otherwise to the keyword arguments dictionary. The returned arguments include default values.

Parameters

signature (inspect.Signature) – The signature of a function to generate canonical arguments for.

Returns

A function that returns a tuple of a tuple of positional arguments and a dict of keyword arguments.

Return type

callable

Examples

Consider the following function.

>>> def f(a, b=4, *, c): pass


The argument_canonicalizer for f is generated as follows:

>>> canon = argument_canonicalizer(inspect.signature(f))


Calling canon with parameter b passed as keyword returns arguments with parameter b as positional argument:

>>> canon(1, c=3, b=2)
((1, 2), {'c': 3})


When calling canon without parameter b the default value is added to the positional arguments:

>>> canon(1, c=3)
((1, 4), {'c': 3})

nutils.types.nutils_hash(data)

Compute a stable hash of immutable object data. The hash is not affected by Python’s hash randomization (see object.__hash__()).

Parameters

data – An immutable object of type bool, int, float, complex, str, bytes, tuple, frozenset, or Ellipsis or None, or the type itself, or an object with a __nutils_hash__ attribute.

Returns

The hash of data.

Return type
class nutils.types.CacheMeta(name, bases, namespace, **kwargs)

Bases: abc.ABCMeta

Metaclass that adds caching functionality to properties and methods listed in the special attribute __cache__. If an attribute is of type property, the value of the property will be computed at the first attribute access and served from cache subsequently. If an attribute is a method, the arguments and return value are cached and the cached value will be used if a subsequent call is made with the same arguments; if not, the cache will be overwritten. The cache lives in private attributes in the instance. The metaclass supports the use of __slots__. If a subclass redefines a cached property or method (in the sense of this metaclass) of a base class, the property or method of the subclass is not automatically cached; __cache__ should be used in the subclass explicitly.

Examples

An example of a class with a cached property:

>>> class T(metaclass=CacheMeta):
...   __cache__ = 'x',
...   @property
...   def x(self):
...     print('uncached')
...     return 1


The print statement is added to illustrate when method x (as defined above) is called:

>>> t = T()
>>> t.x
uncached
1
>>> t.x
1


An example of a class with a cached method:

>>> class U(metaclass=CacheMeta):
...   __cache__ = 'y',
...   def y(self, a):
...     print('uncached')
...     return a


Again, the print statement is added to illustrate when the method y (as defined above) is called:

>>> u = U()
>>> u.y(1)
uncached
1
>>> u.y(1)
1
>>> u.y(2)
uncached
2
>>> u.y(2)
2

class nutils.types.Immutable(*args, **kwargs)

Bases: object

Base class for immutable types. This class adds equality tests, traditional hashing (hash()), nutils hashing (nutils_hash()) and pickling, all based solely on the (positional) intialization arguments, args for future reference. Keyword-only arguments are not supported. All arguments should be hashable by nutils_hash().

Positional and keyword initialization arguments are canonicalized automatically (by argument_canonicalizer()). If the __init__ method of a subclass is decorated with preprocessors (see aspreprocessor()), the preprocessors are applied to the initialization arguments and args becomes the preprocessed positional part.

Examples

Consider the following class.

>>> class Plain(Immutable):
...   def __init__(self, a, b):
...     pass


Calling Plain with equivalent positional or keyword arguments produces equal instances:

>>> Plain(1, 2) == Plain(a=1, b=2)
True


Passing unhashable values to Plain will fail:

>>> Plain([1, 2], [3, 4])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'


This can be solved by adding and applying annotations to __init__. The following class converts its initialization arguments to tuple automaticaly:

>>> class Annotated(Immutable):
...   @apply_annotations
...   def __init__(self, a:tuple, b:tuple):
...     pass


Calling Annotated with either lists of 1, 2 and 3, 4 or tuples gives equal instances:

>>> Annotated([1, 2], [3, 4]) == Annotated((1, 2), (3, 4))
True

class nutils.types.Singleton(*args, **kwargs)

Subclass of Immutable that creates a single instance per unique set of initialization arguments.

Examples

Consider the following class.

>>> class Plain(Singleton):
...   def __init__(self, a, b):
...     pass


Calling Plain with equivalent positional or keyword arguments produces one instance:

>>> Plain(1, 2) is Plain(a=1, b=2)
True


Consider the folling class with annotations.

>>> class Annotated(Singleton):
...   @apply_annotations
...   def __init__(self, a:tuple, b:tuple):
...     pass


Calling Annotated with either lists of 1, 2 and 3, 4 or tuples gives a single instance:

>>> Annotated([1, 2], [3, 4]) is Annotated((1, 2), (3, 4))
True

__eq__(self, value, /)

Return self==value.

nutils.types.strictint(value)

Converts any type that is a subclass of numbers.Integral (e.g. int and numpy.int64) to int, and fails otherwise. Notable differences with the behavior of int:

Examples

>>> strictint(1), type(strictint(1))
(1, <class 'int'>)
>>> strictint(numpy.int64(1)), type(strictint(numpy.int64(1)))
(1, <class 'int'>)
>>> strictint(1.0)
Traceback (most recent call last):
...
ValueError: not an integer: 1.0
>>> strictint('1')
Traceback (most recent call last):
...
ValueError: not an integer: '1'

nutils.types.strictfloat(value)

Converts any type that is a subclass of numbers.Real (e.g. float and numpy.float64) to float, and fails otherwise. Notable difference with the behavior of float:

Examples

>>> strictfloat(1), type(strictfloat(1))
(1.0, <class 'float'>)
>>> strictfloat(numpy.float64(1.2)), type(strictfloat(numpy.float64(1.2)))
(1.2, <class 'float'>)
>>> strictfloat(1.2+3.4j)
Traceback (most recent call last):
...
ValueError: not a real number: (1.2+3.4j)
>>> strictfloat('1.2')
Traceback (most recent call last):
...
ValueError: not a real number: '1.2'

nutils.types.strictstr(value)

Returns value unmodified if it is a str, and fails otherwise. Notable difference with the behavior of str:

Examples

Passing a str to strictstr() works:

>>> strictstr('spam')
'spam'


Passing an int will fail:

>>> strictstr(1)
Traceback (most recent call last):
...
ValueError: not a 'str': 1

class nutils.types.strict(*args, **kwargs)

Bases: object

Type checker. The function strict[cls](value) returns value unmodified if value is an instance of cls, otherwise a ValueError is raised.

Examples

The strict[int] function passes integers unmodified:

>>> strict[int](1)
1


Other types fail:

>>> strict[int]('1')
Traceback (most recent call last):
...
ValueError: expected an object of type 'int' but got '1' with type 'str'

__weakref__

list of weak references to the object (if defined)

class nutils.types.tuple(*args, **kwargs)

Bases: tuple

Wrapper of tuple that supports a user-defined item constructor via the notation tuple[I], with I the item constructor. This is shorthand for lambda items: tuple(map(I, items)). The item constructor should be any callable that takes one argument.

Examples

A tuple with items processed with strictint():

>>> tuple[strictint]((False, 1, 2, numpy.int64(3)))
(0, 1, 2, 3)


If the item constructor raises an exception, the construction of the tuple failes accordingly:

>>> tuple[strictint]((1, 2, 3.4))
Traceback (most recent call last):
...
ValueError: not an integer: 3.4

class nutils.types.frozendict(base)

An immutable version of dict. The frozendict is hashable and both the keys and values should be hashable as well.

Custom key and value constructors can be supplied via the frozendict[K,V] notation, with K the key constructor and V the value constructor, which is roughly equivalent to lambda *args, **kwargs: {K(k): V(v) for k, v in dict(*args, **kwargs).items()}.

Examples

A frozendict with strictstr() as key constructor and strictfloat() as value constructor:

>>> frozendict[strictstr,strictfloat]({'spam': False})
frozendict({'spam': 0.0})


Similar but with non-strict constructors:

>>> frozendict[str,float]({None: '1.2'})
frozendict({'None': 1.2})


Applying the strict constructors to invalid data raises an exception:

>>> frozendict[strictstr,strictfloat]({None: '1.2'})
Traceback (most recent call last):
...
ValueError: not a 'str': None

class nutils.types.frozenmultiset(items)

An immutable multiset. A multiset is a generalization of a set: items may occur more than once. Two mutlisets are equal if they have the same set of items and the same item multiplicities.

A custom item constructor can be supplied via the notation frozenmultiset[I], with I the item constructor. This is shorthand for lambda items: frozenmultiset(map(I, items)). The item constructor should be any callable that takes one argument.

Examples

>>> a = frozenmultiset(['spam', 'bacon', 'spam'])
>>> b = frozenmultiset(['sausage', 'spam'])


The frozenmultiset objects support +, - and & operators:

>>> a + b
frozenmultiset(['spam', 'bacon', 'spam', 'sausage', 'spam'])
>>> a - b
frozenmultiset(['bacon', 'spam'])
>>> a & b
frozenmultiset(['spam'])


The order of the items is irrelevant:

>>> frozenmultiset(['spam', 'spam', 'eggs']) == frozenmultiset(['spam', 'eggs', 'spam'])
True


The multiplicities, however, are not:

>>> frozenmultiset(['spam', 'spam', 'eggs']) == frozenmultiset(['spam', 'eggs'])
False

__and__(self, other)

Return a frozenmultiset with elements from the left and right hand sides with strict positive multiplicity, where the multiplicity is the minimum of multiplicitie of the left and right hand side.

__add__(self, other)

Return a frozenmultiset with elements from the left and right hand sides with a multiplicity equal to the sum of the left and right hand sides.

__sub__(self, other)

Return a frozenmultiset with elements from the left hand sides with a multiplicity equal to the difference of the multiplicity of the left and right hand sides, truncated to zero. Elements with multiplicity zero are omitted.

__and__(self, other)

Return a frozenmultiset with elements from the left and right hand sides with strict positive multiplicity, where the multiplicity is the minimum of multiplicitie of the left and right hand side.

__add__(self, other)

Return a frozenmultiset with elements from the left and right hand sides with a multiplicity equal to the sum of the left and right hand sides.

__sub__(self, other)

Return a frozenmultiset with elements from the left hand sides with a multiplicity equal to the difference of the multiplicity of the left and right hand sides, truncated to zero. Elements with multiplicity zero are omitted.

class nutils.types.frozenarray(base, dtype=None, copy=True)

An immutable version (and drop-in replacement) of numpy.ndarray.

Besides being immutable, the frozenarray differs from numpy.ndarray in (in)equality tests. Given two frozenarray objects a and b, the test a == b returns True if both arrays are equal in its entirety, including dtype and shape, while the same test with numpy.ndarray objects would give a boolean array with element-wise thruth values.

The constructor with predefined dtype argument can generated via the notation frozenarray[dtype]. This is shorthand for lambda base: frozenarray(base, dtype=dtype).

Parameters
• base (numpy.ndarray or array-like) – The array data.

• dtype – The dtype of the array or None.

• copy (bool) – If base is a frozenarray and the dtype matches or is None, this argument is ignored. If base is a numpy.ndarray and the dtype matches or is None and copy is False, base is stored as is. Otherwise base is copied.

class nutils.types.c_array(*args, **kwargs)

Bases: object

Converts an array-like object to a ctypes array with a specific dtype. The function c_array[dtype](array) returns array unmodified if array is already a ctypes array. If array is a numpy.ndarray, the array is converted if the dtype is correct and the array is contiguous; otherwise ValueError is raised. Otherwise, array is first converted to a contiguous numpy.ndarray and then converted to ctypes array. In the first two cases changes made to the ctypes array are reflected by the array argument: both are essentially views of the same data. In the third case, changes to either array or the returned ctypes array are not reflected by the other.

__weakref__

list of weak references to the object (if defined)

class nutils.types.attributes(**args)

Bases: object

Dictionary-like container with attributes instead of keys, instantiated using keyword arguments:

>>> A = attributes(foo=10, bar=True)
>>> A
attributes(bar=True, foo=10)
>>> A.foo
10

__weakref__

list of weak references to the object (if defined)

class nutils.types.unit(*args, **units)

Bases: object

Framework for physical units.

The unit class provides a basic framework for specifying values with physical units using readable notation such as 2.5km/h. The system ensures that values are consistent with a measurement system derived from base units, but it does impose or even preload one such system. Instead a derived class, created using either unit.create() or class arguments, should specify the units and scales relevant for the situation to which it is applied.

Once units are defined, the formal syntax for instantiating a quantity is:

<quantity> ::= <number> <units> | <number> <operator> <units>
<number>   ::= "" | <integer> | <integer> "." <integer>
; Numerical value, allowing for decimal fractions but not
; scientific notation. An empty number is equivalent to 1.
<units>    ::= <unit> | <unit> <operator> <units>
<unit>     ::= <prefix> <name> <power>
<prefix>   ::= "" | "h" | "k" | "M" | "G" | "T" | "P" | "E" | "Z" | "Y"
| "d" | "c" | "m" | "μ" | "n" | "p" | "f" | "a" | "z" | "y"
; Single character prefix to indicate a multiple or fraction
; of the unit. All SI prefixes are supported except for deca.
; An empty prefix signifies no scaling.
<name>     ::= <string>
; One of the defined units, case sensitive, containing Latin
; or Greek symbols.
<power>    ::= "" | <integer>
; Integer power to which to raise the unit. An empty power is
; equivalent to 1.
<operator> ::= "*" | "/"
; Multiplication or division.


With the prefix and unit name sharing an alphabet there is potential for ambiguities (is it mol or micro-ol?). These are resolved using the simple logic that the first character is considered part of the unit if this unit exists; otherwise it is considered a prefix.

classmethod create(*args, **units)

Alternative constructor for backwards compatibility.

__init__(*args, **units)

Create new unit type.

The unit system is defined via variable keyword arguments, with every unit specified either as a direct numerical value or as a string referencing other units using the standard expression syntax. Ultimately every unit should be resolvable to a numerical value by tracing its dependencies.

The following example defines a subset of the SI system. Note that we cannot use prefixes on the receiving end of a definition for reasons of ambiguity, hence the definition of a gram as 1/1000:

>>> SI = unit(m=1, s=1, g=1e-3, N='kg*m/s2', Pa='N/m2')
>>> SI('2km')
2000.0
>>> SI('2g')
0.002

Parameters
• name (str (optional, positional only)) – Name of the new class object.

• **units – Unit definitions.

Returns

The newly created (uninitiated) unit class.

__getitem__(self, unit)

Create subclass of float with custom stringly loads, dumps methods.

__call__(self, s)

Create subclass of float and instantiate.

__weakref__

list of weak references to the object (if defined)