sidekick.properties

Functions in this module are helpers intended to create convenient and declarative idioms when declaring classes. Perhaps it is not entirely correct calling them “functional”, but since some patterns such as lazy properties are common in functional libraries, Sidekick has a module for doing that.

Properties and descriptors

lazy([func]) Mark attribute that is initialized at first access rather than during instance creation.
alias(attr, *, mutable, transform, prepare) An alias to another attribute.
delegate_to(attr, mutable) Delegate access to an inner variable.
property([fget, fset, fdel, doc]) A Sidekick-enabled property descriptor.

API reference

sidekick.properties.lazy(func=None, *, shared: bool = False, name: str = None, attr_error: Union[Type[Exception], bool] = False)[source]

Mark attribute that is initialized at first access rather than during instance creation.

Usage is similar to @property, although lazy attributes do not override setter and deleter methods, allowing instances to write to the attribute.

Optional Args:
shared:
A shared attribute behaves as a lazy class variable that is shared among all classes and instances. It differs from a simple class attribute in that it is initialized lazily from a function. This can help to break import cycles and delay expensive global initializations to when they are required.
name:
By default, a lazy attribute can infer the name of the attribute it refers to. In some exceptional cases (when creating classes dynamically), the inference algorithm might fail and the name attribute must be set explicitly.
attr_error (Exception, bool):
If False or an exception class, re-raise attribute errors as the given error. This prevent erroneous code that raises AttributeError being mistakenly interpreted as if the attribute does not exist.

Examples

import math

class Vec:
    @sk.lazy
    def magnitude(self):
        print('computing...')
        return math.sqrt(self.x**2 + self.y**2)

    def __init__(self, x, y):
        self.x, self.y = x, y

Now the magnitude attribute is initialized and cached upon first use:

>>> v = Vec(3, 4)
>>> v.magnitude
computing...
5.0

The attribute is writable and apart from the deferred initialization, it behaves just like any regular Python attribute.

>>> v.magnitude = 42
>>> v.magnitude
42

Lazy attributes can be useful either to simplify the implementation of __init__ or as an optimization technique that delays potentially expensive computations that are not always necessary in the object’s lifecycle. Lazy attributes can be used together with quick lambdas for very compact definitions:

import math
from sidekick import placeholder as _

class Square:
    area = sk.lazy(_.width * _.height)
    perimeter = sk.lazy(2 * (_.width + _.height))
sidekick.properties.delegate_to(attr: str, mutable: bool = False)[source]

Delegate access to an inner variable.

A delegate is an alias for an attribute of the same name that lives in an inner object of an instance. This is useful when the inner object contains the implementation (remember the “composition over inheritance mantra”), but we want to expose specific interfaces of the inner object.

Parameters:
  • attr – Name of the inner variable that receives delegation. It can be a dotted name with one level of nesting. In that case, it associates the property with the sub-attribute of the delegate object.
  • mutable – If True, makes the the delegation read-write. It writes new values to attributes of the delegated object.

Examples

class Queue:
    pop = sk.delegate_to('_data')
    push = sk.delegate_to('_data.append')

    def __init__(self, data=()):
        self._data = list(data)

    def __repr__(self):
        return f'Queue({self._data})'

Now Queue.pop simply redirects to the pop method of the ._data attribute, and Queue.push searches for ._data.append

>>> q = Queue([1, 2, 3])
>>> q.pop()
3
>>> q.push(4); q
Queue([1, 2, 4])
sidekick.properties.alias(attr: str, *, mutable: bool = False, transform: Optional[Callable] = None, prepare: Optional[Callable] = None)[source]

An alias to another attribute.

Aliasing is another simple form of self-delegation. Aliases are views over other attributes in the instance itself:

Parameters:
  • attr – Name of aliased attribute.
  • mutable – If True, makes the alias mutable.
  • transform – If given, transform output by this function before returning.
  • prepare – If given, prepare input value before saving.

Examples

class Queue(list):
    push = sk.alias('pop')

This exposes two additional properties: “abs_value” and “origin”. The first is just a read-only view on the “magnitude” property. The second exposes read and write access to the “start” attribute.

sidekick.properties.property(fget=None, fset=None, fdel=None, doc=None)[source]

A Sidekick-enabled property descriptor.

It is a drop-in replacement for Python’s builtin properties. It behaves similarly to Python’s builtin, but also accepts quick lambdas as input functions. This allows very terse declarations:

from sidekick.api import placeholder as _

class Vector:
    sqr_radius = sk.property(_.x**2 + _.y**2)

lazy() is very similar to property. The main difference between both is that properties are not cached and hence the function is re-executed at each attribute access. The desired behavior will depend a lot on what you want to do.