Here's a quick and easy one.
attrs supports frozen classes. Frozen classes are cool for a multitude of reasons, but they're a tiny bit slower to instantiate compared to non-frozen classes, because they need to perform some additional checks and avoiding these checks in the __init__
uses a tiny bit of time. (We're talking slotted classes here; which are awesome and the default in attrs nowadays.)
But there's a way to avoid this overhead and make frozen classes be the exact same speed as ordinary classes. The only caveat is: you have to use type-checking.
Create a file somewhere in your code base and put this in it:
from functools import partial
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from attrs import frozen
else:
from attrs import define
frozen = partial(define, unsafe_hash=True)
Now import frozen
from this module and use it just like you'd use attrs.define
or attrs.frozen
. You're done!
This technique should also work for dataclasses, with a slight adjustment left as an exercize to the reader.
The eagle-eyed reader might notice that we're actually bamboozling the type-checker: your classes won't actually be frozen at runtime. The kicker is: they don't actually need to be.
As long as you're running one on your codebase, the typechecker is the actual thing that'll prevent you from mutating your instances. The unsafe_hash=True
will make the classes hashable, and it's only unsafe if you mutate them after construction, which you won't. I guess you'll have to be careful when using a REPL or a different context where a typechecker might not hold sway, but I think that's not too big of an issue.
If you're still unconvinced, I'll leave you with two final thoughts: the memory in your computer is, ultimately, mutable. What makes immutable data structures in other languages immutable is just the amount of hoops you have to jump through to apply a mutation. This also demonstrates a basic technique statically-compiled languages use to be fast: move part of the work out of runtime into a separate, pre-runtime step. Which is exactly what we've done here.
Happy New Year!