Arraylias User Guide#

The main class of the Arraylias package is the Alias class. This is used to build an aliased module structure for 1 or more libraries and write generic code that can automatically dispatch to the appropriate function in one of those libraries based on the type of the first argument of the called function.

Full library sub-module paths are supported, and libraries can be extended by registering custom modules, and modules can be extended by registering custom functions.

Default NumPy and SciPy Aliases#

The simplest way to use Arraylias is to use the numpy_alias() and scipy_alias() functions to return a pre-configured Alias with NumPy module or SciPy module syntax respectively.

If the appropriate Python libraries are installed on the system this will include pre-registered support for the following libraries using the NumPy module interface

See the Using an Alias section for a guide on using an Alias in your code.

For extending with support for additional libraries refer to the Building an Alias section.

Using an Alias#

Given a configured Alias instance, an alias to a given path is returned using the Alias.__call__() method. This will return an AliasedPath or AliasedModule to the specified path.

Automatic dispatching#

An AliasedPath stores an unresolved path to a function. Whether this function exists in any libraries is not resolved until it is called. When called the type of the first argument will be used to infer the library to dispatch on, and if a function exists for that path it will be returned and called.

For example

# Return the default NumPy Alias
alias = numpy_alias()
eig = alias("linalg.eig")
evals, evecs = eig(a)

If an Alias is called with no arguments this will return an AliasedModule to the base path of the alias. This can be traversed to submodules or functions using attribute based access such as:

unp = alias()  # Returns AliasedModule to base
evals, evecs = unp.linalg.eig(a)

Aliased paths can also be treated as modules until they are resolved:

la = alias("linalg")
evals, evecs = la.eig(a)

Dispatching to a specific library#

Dispatching to a specific library function or module can be done by specifying a path including the library, or using the like kwarg of the call method.

# The following are equivalent
tensordot = alias("tensordot", like="numpy")

np = alias(like="numpy")
tensordot = np.tensordot

The like kwarg can also be passed a type or object to infer the library from, for example

tensordot = alias("tensordot", like=a)

When dispatching to a specific library the returned function will be the actual library function or registered function for that path instead of an AliasedPath instance. Note however that for modules the return type will be a AliasedModule instance instead of the actual library module. This is to allow access to custom registered functions and sub-modules in the Alias for that library.

Building an Alias#

Registering Types#

Types are registered to a specific library for dispatching using the Alias.register_type() method. Registering a type a second time will override the previous library with the new library. Any subclasses of registered types will also be matched for dispatching if they are not separately registered.

For example to register NumPy ndarrays:

alias.register_type(numpy.ndarray, "numpy")

The Alias.registered_types() and Alias.registered_libs() methods can be used to return aa tuple of all registered types and libraries respectively.

Registering Modules#

The Alias.register_module() method can be used to register a module for dispatching aliased functions and modules for a library. By default modules are registered to the base path of that library if a custom path is not provided.

For example to register the base NumPy module, which will also allow path based access to all sub-modules accessible from numpy.

alias.register_module(numpy, "numpy")

We can also use this method to modify the default NumPy path, for example to add SciPy linear algebra functions to the NumPy linear algebra path:

alias.register_module(scipy.linalg, "numpy", path="linalg")

Note that the default numpy_alias() does not include SciPy functions. There is a separate scipy_alias() that can be used to initialize a SciPy alias.

Registering Functions#

Individual functions are registered using the Alias.register_function() method.

alias.register_function(some_function, lib="library")

The Alias.register_function() can also be used as a decorator like

@alias.register_function(lib="numpy")
def foo(a, x, b):
   return a * x + b

By default the name of the function will be used as its path, a custom name can be provided by using the path kwarg

@alias.register_function(lib="numpy", path="line")
def _(a, x, b):
   return a * x + b

Note that a function can be registered to a specific submodule by including it in the path. These modules do not even need to exist in the library, they will still be traversable by the alias. Eg

@alias.register_function(lib="numpy", path="objectives.linear.line")
def _(a, x, b):
   return a * x + b

# Evaluate added function
unp = alias("numpy")
y = unp.objectives.linear.line(a, x, b)

If the path kwarg is not provided, the name of the function will be used as the path.

Registering Fallback Functions#

The Alias.register_fallback() can be used to register a fallback function that will be invoked if a match to a specific function path cannot be found for the dispatched library. Like Alias.register_function() it can also be used as a function decorator.

Typically this would be used to implement a generic method for a custom function that works for all registered libraries, and then also registering a specialized version of the function for a specific library using the Alias.register_function() method.

Registering Default Functions#

The Alias.register_default() can be used to register a default function that will be invoked if the type of the first argument of a called AliasedPath function does not match any registered library types.

Typically this would be used to register a default implementation of a function that may take other types than arrays as its first argument, for example this is used by numpy_alias() to register numpy.array and numpy.asarray as default functions:

alias.register_default(numpy.array)

unp = alias()
a = unp.array(SomeCustomClass())