Objects¶
Accretive objects allow for the creation of instances, such that new attributes
can be assigned at any time, but cannot be reassigned or deleted after
assignment. Accretive objects can be created via decoration (by @accretive
)
or inheritance (from Object
).
>>> from accretive import Object, accretive
Decorator¶
The @accretive
decorator can apply accretive attribute behavior to any
class:
>>> @accretive
... class Config:
... def __init__( self, debug = False ):
... self.debug = debug
...
>>> config = Config( debug = True )
>>> config.debug
True
>>> config.verbose = True # Add new attribute
>>> config.verbose
True
As with the Object
class, attributes cannot be modified or deleted once
set:
>>> config.debug = False
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'debug'.
Mutable Attributes¶
The mutables
argument can allow some attributes to remain mutable after
assignment.
>>> @accretive( mutables = ( 'version', ) )
... class VersionedConfig:
... def __init__( self, name, version ):
... self.name = name
... self.version = version
...
>>> config = VersionedConfig( 'MyApp', '1.0.0' )
Reassignment of mutable attribute:
>>> config.version = '1.0.1' # This works fine
>>> config.version
'1.0.1'
Deletion of mutable attribute:
>>> del config.version # This works with mutable attributes
>>> hasattr( config, 'version' )
False
Docstrings¶
The docstring
argument can set or override the docstring of the decorated
class. This is useful when docstrings need to be computed dynamically:
>>> @accretive( docstring = 'A configuration class with custom documentation.' )
... class DocumentedConfig:
... '''Original docstring that will be replaced.'''
... def __init__( self, name ):
... self.name = name
...
>>> print( DocumentedConfig.__doc__ )
A configuration class with custom documentation.
Data Classes¶
The @accretive
decorator works well with unfrozen Python data classes:
>>> from dataclasses import dataclass
>>>
>>> @accretive
... @dataclass
... class ServerConfig:
... host: str
... port: int = 8080
...
>>> server = ServerConfig( host = 'localhost' )
>>> server.host
'localhost'
>>> server.port
8080
>>> server.secure = True # Add new attribute
>>> server.host = '127.0.0.1' # Attempt to modify
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'host'.
Slotted Classes¶
The @accretive
decorator works with classes which use __slots__
for
attribute storage. Remember to include the _behaviors_
slot:
>>> @accretive
... class SlottedConfig:
... __slots__ = ( 'debug', '_behaviors_' )
...
... def __init__( self, debug = False ):
... self.debug = debug
...
>>> config = SlottedConfig( debug = True )
>>> config.debug
True
>>> config.debug = False
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'debug'.
Compatibility¶
The @accretive
decorator cannot be applied to classes that define their own
__setattr__
or __delattr__
methods, as this would conflict with the
immutability enforcement:
>>> @accretive # This will fail
... class Mutable:
... def __setattr__( self, name, value ):
... # Custom attribute setting logic
... super( ).__setattr__( name, value )
Traceback (most recent call last):
...
accretive.exceptions.DecoratorCompatibilityError: Cannot decorate class 'Mutable' which defines '__setattr__'.
Base Class¶
The Object
base class provides accretive behavior when instantiated
directly:
>>> obj = Object( )
>>> obj.name = 'example'
>>> obj.name
'example'
or through inheritance:
>>> class CustomConfig( Object ):
... ''' A custom configuration with accretive attributes. '''
... pass
...
>>> custom = CustomConfig( )
>>> custom.setting = 'enabled'
Once attributes are set, they cannot be modified or deleted:
>>> obj.name = 'modified'
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'name'.
>>> del custom.setting
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'setting'.
New attributes can be added at any time:
>>> obj.version = '1.0'
>>> obj.version
'1.0'
Warning
When working with built-in types, such as exception types, in multiple
inheritance hierarchies, avoid using the Object
base class which uses
__slots__
. Instead, apply the @accretive
decorator directly to your
class.
Multiple Inheritance Considerations¶
When using the Object
class with multiple inheritance, be aware of
potential layout conflicts with built-in types that have their own memory
layout:
>>> # This would raise a TypeError due to memory layout conflict
>>> # class InvalidCombination( BaseException, Object ):
>>> # pass
Instead, use the @accretive
decorator directly:
>>> @accretive
... class ValidException( BaseException ):
... ''' An exception with accretive behavior. '''
... pass
...
>>> ex = ValidException( 'Something went wrong' )
>>> ex.context = 'Additional information'
>>> ex.context
'Additional information'
>>> ex.context = 'Changed information'
Traceback (most recent call last):
...
accretive.exceptions.AttributeImmutabilityError: Cannot reassign or delete existing attribute 'context'.