vivis vivis avatar dev

> thoughts on AI, code, and everything in between

Koan 13: The Empty Bowl

, ,

In monasteries bowls are often used for meditation rituals. Some are empty, others hold offerings: coins, water, and small tokens of care. In Python, dataclasses behave much like these bowls.

Each dataclass instance is a bowl; the data it carries are the offerings inside. How Python evaluates equality and hashing depends on both the contents and the form of the bowl itself.

Part 1: What is a Dataclass?

Dataclasses, introduced in Python 3.7, are a convenience feature. They save you from writing boilerplate code when you need simple classes that hold data.

For example, without dataclasses you might write:

And with a dataclass it becomes:

Python automatically creates the __init__, __eq__, and __repr__ functions for you when you use the dataclass decorator.

Part 2: The Empty Bowl

Consider a bowl with no marked offerings:

Although values are assigned on the class, there are no annotated fields. When Python evaluates this bowl:

Only attributes declared with annotations are considered the recognized contents of the bowl, contributing to equality and hash calculations. Adding type annotations transforms the bowl:

Now the offerings are acknowledged, and hashing reflects their presence. Two bowls with identical annotated fields are equal, and their hashes correspond to their contents.

Part 2: Comparing Different Bowls

Next, imagine two bowls, each holding the same offerings:


If you're enjoying this post, consider subscribing for more posts like this:


The bowls may contain identical coins and water, yet they differ in form. Python compares both the contents and the type of the object:

Equality requires both the offerings and the vessel to match. Hashing, on the other hand, considers only the annotated fields.

Part 3: Fields vs Class Variables

The distinction between annotated fields and class variables is crucial. Only annotated fields are considered offerings within the bowl. Class variables exist on the outside: they do not contribute to equality or hashing.


Part 4: Mutable vs Frozen Dataclasses

You may have noticed that we are delcaring classes with frozen=True. Dataclasses can be created mutable or frozen:

  • Mutable dataclasses allow their contents to change after creation. This is the default.

  • Frozen dataclasses make the contents immutable, like sealing the offerings inside a bowl.

Frozenness also affects hashability. Only frozen dataclasses are hashable by default, because mutable bowls could change after being placed in a set or used as dict keys.

In Koan 8 we learnt that the hashability of a class is determined by the implementation of the __hash__ function. When a dataclass is declared frozen, Python automatically creates a __hash__ function for you.


Part 5: Default Values and Factories

Default values make bowls easier to create without specifying every offering:

For mutable fields like lists or dictionaries, a default factory is necessary. Using a default argument with a mutable object can lead to shared state between instances:

This is because default arguments are evaluated once at definition time, not execution time, as we learnt in Koan 3.

Instead, a default_factory creates a new object for each instance:

Now each bowl has its own independent list of offerings. Python calls the factory function at the time the instance is created, ensuring the contents of each bowl remain separate.

This distinction prevents subtle bugs and preserves the integrity of each bowl’s contents.


Offering the Bowl

A bowl without marked offerings is indistinguishable from another empty bowl of the same type. Two bowls may contain identical offerings, yet remain separate if their forms differ. Hashing depends only on the contents, while equality considers both the contents and the form.

Through careful attention to fields, mutability, defaults, and the auto-generated methods, dataclasses provide a simple, predictable structure for Python data objects.

Thanks for reading Python Koans! Subscribe for free to receive new posts and support my work.