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'.