
Creating immutable objects with __slots__
If we are not able to set an attribute or create a new one, then the object is immutable. The following is what we'd like to see in interactive Python:
>>> c= card21(1,'♠') >>> c.rank= 12 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 30, in __setattr__ TypeError: Cannot set rank >>> c.hack= 13 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 31, in __setattr__ AttributeError: 'Ace21Card' has no attribute 'hack'
The preceding code shows that we are not allowed to change an attribute or add one to this object.
We need to make two changes to a class definition for this to work. We'll omit much of the class and focus on just the three features that make an object immutable, as follows:
class BlackJackCard: """Abstract Superclass""" __slots__ = ( 'rank', 'suit', 'hard', 'soft' ) def __init__( self, rank, suit, hard, soft ): super().__setattr__( 'rank', rank ) super().__setattr__( 'suit', suit ) super().__setattr__( 'hard', hard ) super().__setattr__( 'soft', soft ) def __str__( self ): return "{0.rank}{0.suit}".format( self ) def __setattr__( self, name, value ): raise AttributeError( "'{__class__.__name__}' has no attribute '{name}'".format( __class__= self.__class__, name= name ) )
We made three significant changes:
- We set
__slots__
to the names of only the allowed attributes. This turns off the internal__dict__
feature of the object and limits us to just the attributes and no more. - We defined
__setattr__()
to raise an exception rather than do anything useful. - We defined
__init__()
to use the superclass version of__setattr__()
so that values can be properly set in spite of the absence of a working__setattr__()
method in this class.
With some care, we can bypass the immutability feature if we work at it.
object.__setattr__(c, 'bad', 5)
That brings us to a question. "How can we prevent an "evil" programmer from bypassing the immutability feature?" The question is silly. We can't stop the evil programmer. Another equally silly question is, "Why would some evil programmer write all that code to circumvent immutability?". We can't stop the evil programmer from doing evil things.
If this imaginary programmer doesn't like immutability in a class, they can modify the definition of the class to remove the redefinition of __setattr__()
. The point of an immutable object like this is to guarantee __hash__()
returning a consistent value and not to prevent people from writing rotten code.