Standard Classes

Immutable classes are similar to standard Python classes, but prevent any modification of class attributes after creation. This makes them ideal for defining constants, enumerations, or configurations that should be completely immutable.

>>> from frigid import Class

Basic Usage

Immutable classes can be defined using the Class metaclass. Attributes must be defined during class creation.

>>> class Constants( metaclass = Class ):
...     PI = 3.14159
...     E = 2.71828
...     PHI = 1.61803

Once defined, these attributes are protected from any modification:

>>> Constants.PI = 3.14  # Attempt to modify
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'PI'.

They cannot be deleted:

>>> del Constants.E  # Attempt to delete
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'E'.

And new attributes cannot be added after class creation:

>>> Constants.SQRT2 = 1.4142  # Attempt to add
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'SQRT2'.

Decorator Support

Classes can be modified during creation using decorators. This allows for programmatic addition of attributes before the class becomes immutable.

>>> def add_computed_constants( cls ):
...     cls.TAU = cls.PI * 2
...     return cls
...
>>> class CircleConstants( metaclass = Class, decorators = ( add_computed_constants, ) ):
...     PI = 3.14159

The decorator-added attributes become part of the immutable class:

>>> CircleConstants.TAU
6.28318
>>> CircleConstants.TAU = 6.28  # Attempt to modify
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'TAU'.

Dynamic Docstrings

Classes can be given docstrings dynamically at creation time, which can be useful for generating documentation programmatically:

>>> docstring = 'Configuration for database connection.'
>>> class DBConfig( metaclass = Class, docstring = docstring ):
...     ''' This docstring will be replaced. '''
...     HOST = 'localhost'
...     PORT = 5432
>>> DBConfig.__doc__ == docstring
True

Abstract Base Classes

The ABCFactory metaclass creates immutable abstract base classes. This is particularly useful for defining stable interfaces that should not change after definition.

>>> from frigid import ABCFactory
>>> from abc import abstractmethod

>>> class DataStore( metaclass = ABCFactory ):
...     @abstractmethod
...     def get( self, key ): pass
...
...     @abstractmethod
...     def put( self, key, value ): pass
...
...     ENCODING = 'utf-8'

The abstract methods and class attributes are protected:

>>> # Cannot modify abstract interface
>>> def new_method( self ): pass
>>> DataStore.list_keys = new_method
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'list_keys'.
>>> # Cannot modify class attributes
>>> DataStore.ENCODING = 'ascii'
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'ENCODING'.

Protocol Classes

The ProtocolClass metaclass creates immutable protocol classes, which is useful for defining static type interfaces.

>>> from frigid import ProtocolClass
>>> from typing import Protocol

>>> class Comparable( Protocol, metaclass = ProtocolClass ):
...     def __lt__( self, other ) -> bool: ...
...     def __gt__( self, other ) -> bool: ...
...
...     ORDERING = 'natural'

The protocol interface is protected from modification:

>>> # Cannot modify protocol interface
>>> def eq( self, other ) -> bool: ...
>>> Comparable.__eq__ = eq
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute '__eq__'.
>>> # Cannot modify class attributes
>>> Comparable.ORDERING = 'reverse'
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'ORDERING'.