Objects

The frigid.objects module provides tools for creating objects with immutable attributes. This includes both a base class and a decorator that can be applied to existing classes.

Base Class

The Object class serves as a base for creating immutable objects. Attributes must be set in the derived class’s __init__ method before calling super().__init__(), after which the object becomes immutable.

>>> from frigid import Object

Here’s an example of a point class with immutable coordinates:

>>> class Point( Object ):
...     def __init__( self, x, y ):
...         self.x = x
...         self.y = y
...         super( ).__init__( )

The object behaves normally during initialization:

>>> point = Point( 3, 4 )
>>> point.x
3

After initialization, attributes cannot be modified:

>>> point.x = 5
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'x'.

They cannot be deleted:

>>> del point.y
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'y'.

And new attributes cannot be added:

>>> point.z = 0
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'z'.

Immutable Decorator

The @immutable decorator can be applied to existing classes to make their instances immutable after initialization. This is particularly useful for classes that need to be immutable but have specific initialization requirements.

>>> from frigid import immutable

Here’s an example of a temperature class that validates its value during initialization:

>>> @immutable
... class Temperature:
...     def __init__( self, kelvin ):
...         if kelvin < 0:
...             raise ValueError( "Temperature cannot be below absolute zero" )
...         self.kelvin = kelvin
...         self.celsius = kelvin - 273.15
...         self.fahrenheit = self.celsius * 9/5 + 32

The class works normally during initialization:

>>> water_boiling = Temperature( 373.15 )
>>> water_boiling.celsius
100.0

But becomes immutable afterwards:

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

The decorator preserves the class’s validation logic:

>>> impossible = Temperature( -1 )  # Attempt invalid initialization
Traceback (most recent call last):
...
ValueError: Temperature cannot be below absolute zero

Decorator Compatibility

The @immutable decorator cannot be applied to classes that define their own __setattr__ or __delattr__ methods, as this would conflict with the immutability enforcement:

>>> @immutable  # This will fail
... class Mutable:
...     def __setattr__( self, name, value ):
...         # Custom attribute setting logic
...         super().__setattr__( name, value )
Traceback (most recent call last):
...
frigid.exceptions.DecoratorCompatibilityError: Cannot decorate class 'Mutable' which defines '__setattr__'.

Slots Support

The @immutable decorator works with classes that use __slots__ for attribute storage. Remember to include the _behaviors_ slot:

>>> @immutable
... class Vector:
...     __slots__ = ( 'x', 'y', 'z', '_behaviors_' )
...
...     def __init__( self, x, y, z ):
...         self.x = x
...         self.y = y
...         self.z = z

The slots-based class behaves the same as one using __dict__:

>>> v = Vector( 1, 2, 3 )
>>> v.x = 0  # Attempt to modify
Traceback (most recent call last):
...
frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'x'.