Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Hugh Jeremy
Hugh Jeremy

Posted on

     

Immutable objects in Python

Python is a beautifully flexible beast. Class properties are variable, which means we can change them from anything to anything at any time.

classFlexible:piece="hello world"instance=Flexible()print(instance.piece)# prints “hello world”instance.piece=42print(instance.piece)# prints “42”
Enter fullscreen modeExit fullscreen mode

Sometimes, we might want to trade some flexibility for safety. Humans are fallible, forgetful, and fickle beasts. Programmers are also humans. We make mistakes more often than we would like to admit.

Fortunately, Python gives us the tools protect ourselves against ourselves. Where we want to, we can trade flexibility for safety. You might wish to protect yourself by creatingimmutable objects : Instances of a class that can’t be modified once they are created.

In this article, we will seek immutability of properties. That is, we will stop ourselves from being able to change the.piece property of aFlexible class.

By making our class properties immutable, we eliminate the need to reason about object state. This reduces our cognitive load, and thus the potential for error.

Note that the immutability in this context is different to the immutability discussed byhimank in hisearlier Dev.to post. There, himank talks about immutability from the perspective of memory, an equally valuable but different angle on the broad topic of immutability in general.

Our objective is to achieve immutability from the perspective of the programmer - To explicitly catch cases were we accidentally attempt to mutate a property that we should not. From the perspective of the machine, the property is still perfectly mutable. We aren’t trying to change the way the property behaves in memory, we are trying to protect ourselves from our own stupidity.

To create an immutable property, we will utilise the inbuilt Pythonproperty class.property allows us to define get and set behaviour for a property.

classFlexible:piece=property(lambdas:"hello world"w)instance=Flexible()print(instance.piece)# prints “hello world”Instance.piece=mutated# throws AttributeError
Enter fullscreen modeExit fullscreen mode

Theproperty class takes four parameters. The two we will focus on here arefget andfset. In the above example,lambda s: “hello world” was ourfget, allowing us toprint(instance.piece). The absence offset caused the AttributeError when we attempted to set the value ofinstance.piece to’mutated’.

An AttributeError might be a solid enough reminder to yourself that you’ve accidentally done something dumb. However, you might be working on a project with multiple programmers. Perhaps an AttributeError is not a clear enough warning to others that a property should not change.

For example, a colleague might interpret that AttributeError as a sign that you simply forgot to implementfset. They might merrily edit your class, addingfset, unknowingly opening a Pandora’s Box of state-related bugs.

To give our colleagues as much information as possible, let’s make the immutability explicit. We can do so by subclassingproperty.

classImmutable(property):_MESSAGE="Object state must not be mutated"def__init__(self,get)->None:super(fget,self._set_error)defself._set_error(self,_1,_2)->None:raiseRuntimeError(self._MESSAGE)
Enter fullscreen modeExit fullscreen mode

Now, when we attempt to change a property, we get a clear and unambiguous error.

classFlexible:piece=Immutable(lambdas:"Can't touch this")instance=Flexible()instance.piece="try me"# Raises RuntimeError with clear description
Enter fullscreen modeExit fullscreen mode

Of course, alambda serving a constant is not going to satisfy many requirements. You can supply thefget parameter something more useful. For example, suppose a class maintains some internal state, readable by the whole program. It is crucial to the safe operation of the program that nothing outside the class modifies that state.

classFlexible:_internal_state=42some_state=Immutable(lambdas:s._internal_state)
Enter fullscreen modeExit fullscreen mode

In this case, the rest of the program can safely access the value of_internal_state via thesome_state property. We provide a strong hint to our colleagues that_internal_state is off limits by using the leading underscore: A convention for hinting that a variable be treated as "private". The value returned bysome_state can be changed internally by the class, but it is very hard for a programmer to accidentally modify the state externally.

Other languages might achieve this behaviour in other ways, especially through the use of theprivate keyword. For example, in Swift:

classFlexible{publicprivate(set)varsome_state=42}
Enter fullscreen modeExit fullscreen mode

Unlike Swift and others, Python will not explicitly stop someone from modifying theFlexible state. For example, a colleague could easily execute

instance._internal_state="where is your god now?"
Enter fullscreen modeExit fullscreen mode

That flexibility is a great strength of Python. The point is not to stop anyone doing anything. The point is to provide helpful hints, checks, and clues to stop ourselves from making silly mistakes.

Originally published athughjeremy.com

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
lyfolos profile image
Muhammed H. Alkan
12-13 years old developer.
• Edited on• Edited

Also, you can use python__setattr__ magic method to control whatever will be run when setting an attribute. That will be helpful when making immutable values.

classConstants:def__setattr__(self,name,value):ifnameinself.__dict__:raiseException(f"Cannot change value of{name}.")self.__dict__[name]=valuea=Constants()a.b=2print(a.b)a.b=1print(a.b)

The output will be

2Traceback(mostrecentcalllast):File"python.py",line13,in<module>a.b=1File"python.py",line4,in__setattr__raiseException(f"Cannot change value of{name}.")Exception:Cannotchangevalueofb.

But technically it will still be mutable because

a.__dict__["b"]=1

Python magic methods are really cool, whoever doesn't know about Python's magic methods should readrszalski.github.io/magicmethods/

Thanks for your beautiful post!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I build systems to solve difficult problems. Expert in full-stack software development, hardware deployment, and networking computer systems in extreme environments.
  • Location
    Earth
  • Work
    CTO Procuret
  • Joined

More fromHugh Jeremy

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp