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.
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.
2. 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.
3. 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.
4. 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.
6. 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!
7. 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.
9. 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:
10. 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.
11. 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 …
12. With http://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!
This thread sums up pretty much everything I know about immutability in Python. Don't forget to scroll up :) 👆
You can follow @1st1.
Tip: mention @threader_app on a Twitter thread with the keyword “compile” to get a link to it.
Enjoy Threader? Sign up.
Since you’re here...
... we’re asking visitors like you to make a contribution to support this independent project. In these uncertain times, access to information is vital. Threader gets 1,000,000+ visits a month and our iOS Twitter client was featured as an App of the Day by Apple. Your financial support will help two developers to keep working on this app. Everyone’s contribution, big or small, is so valuable. Support Threader by becoming premium or by donating on PayPal. Thank you.