Yury Selivanov's profile picture
Yury Selivanov
@1st1
View on Twitter

Let's talk about immutability. Python, unlike languages like Clojure, Erlang, and Rust is built entirely around mutable state. I'm going to try to convince you that immutability is awesome and can be effectively used in Python today.

Thread 👇

  1. If an object is immutable it means that its state cannot be changed after it is created. For instance, in Python you can't change a character in a string, or reassign an element of a tuple. Python ships with a few immutable datatypes: str, bytes, tuple/namedtuple, frozenset.
  1. Users can of course create their own immutable datatypes / objects. If an object doesn't have API to mutate its state and all of its public attrubutes are exposed via a readonly @property it is effectively immutable.
  1. Immutable objects are special because that can be "hashable". That means that they implement a hash() method, which, in turn, means that they can be keys in dicts and other mappings.
  1. It's not safe to make a mutable object hashable. Imagine a dictionary key that isn't stable and can be changed at any time! Thus immutability and hashability are closely related things.
  1. That said, it's possible for an immutable collection to be non-hashable. Hashing is a recursive operation, so If an immutable collection contains a mutable object, it becomes non-hashable.
  1. Hashable & immutable objects and collections can be used as cache keys. Therefore functions that receive only immutable arguments and have no side effects can be decorated with @functools.lru_cache to speed things up!
  1. Immutable objects are so easy to reason about; you can cache them & pass them around safely. For example, "inspect.signature()" function returns a Signature object. It has a lot of metadata, all of which is immutable. So it's cached and computed only once per a function!

8a. Another great examples is the new 'contextvars' stdlib module. Context Variables are similar to threadlocal objects, and are used to have local state in async/await code. How can we implement this local state and make it super fast to capture the state?

8b. The answer, of course, is to make it immutable! contextvars store all their state in an immutable collection, so to capture the entire local state you only need to assign it to a variable! Any change to the state creates a new version of the state.

8c. We use the same concept in @edgedatabase, where the schema of the database is stored in an immutable collection. It means that implementing support for transactions, savepoints, and DDL is easy.

  1. Unfortunately Python doesn't have an immutable mapping datatype yet. There's a trick though: types.MappingProxyType. Wrap a dict with it and return it from your API. Without access to the wrapped dict it's impossible to mutate the MappingProxy:
  1. So types.MappingProxyType can be used to expose a read-only dict and that's great. Sadly it's not hashable though, so it can't be used as a cached key etc. There's a solution though. The above mentioned contextvars module required an efficient immutable mapping type.
  1. The implementation of that new mapping type is rather complex, and it would be a shame if we could only use it for contextvars and nothing else. So we created a new library out of it: immutables. https://github.com/magicstack/immutables
  1. With immutables.Map you can easily create immutable and hashable mappings. The Map type is almost as fast as Python dict, with O(log32 N) lookup/update complexity. Maybe we'll have it in Python 3.8 stdlib!
  1. BTW, there's a new trick to easily declare named tuples in Python: typing.NamedTuple. I use it all the time.
  1. Similar to (13), you can also create a frozen dataclass:

This thread sums up pretty much everything I know about immutability in Python. Don't forget to scroll up :) 👆

  1. Bonus. Did you know about namedtuple._replace() method?
Help us raise more money for charities by sharing this page ♥️
Wait! Before you go...
Grab Exclusive Deals for Books, Courses, Software.
100% of Profits Are Donated To Research-Backed Charities.