The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Define developer representation | def __repr__(self): return f'Type({self.value!r})' | View examples |
| Define readable text | def __str__(self): return self.label | View examples |
| Define truth testing | def __bool__(self): return self.is_ready | View examples |
| Define equality | def __eq__(self, other):
return self.value == other.value if isinstance(other, Type) else NotImplemented | View examples |
| Define hashing | def __hash__(self):
return hash((self.first, self.second)) | View examples |
| Decline an operation | return NotImplemented | View examples |
| Define less-than ordering | def __lt__(self, other):
return self.value < other.value if isinstance(other, Type) else NotImplemented | View examples |
| Define addition | def __add__(self, other):
return type(self)(self.value + other.value) | View examples |
| Define reflected addition | def __radd__(self, other):
return type(self)(other + self.value) | View examples |
| Report container length | def __len__(self): return len(self._items) | View examples |
| Support indexed access | def __getitem__(self, key): return self._items[key] | View examples |
| Optimize membership | def __contains__(self, item): return item in self._items | View examples |
| Provide iteration | def __iter__(self): return iter(self._items) | View examples |
| Manage one attribute | value = property(getter, setter) | View examples |
| Handle missing attributes | def __getattr__(self, name): return self.fields[name] | View examples |
| Intercept all instance lookup | def __getattribute__(self, name):
return object.__getattribute__(self, name) | View examples |
| Define descriptor reading | def __get__(self, instance, owner=None):
return self if instance is None else instance.__dict__[self.name] | View examples |
| Learn the assigned name | def __set_name__(self, owner, name): self.name = name | View examples |
Special methods let user-defined classes participate in Python syntax and built-in protocols. Implement the smallest coherent protocol your type actually supports, preserve invariants such as equal objects having equal hashes, and return NotImplemented when another operand deserves a chance. Python usually looks up implicit special methods on the type, so changing them on one instance is not a reliable customization technique.
Step by step
Detailed examples
Separate developer representation, display text, and truth
repr should help identify or reconstruct an object when practical, while str targets readable display and falls back to repr when absent. Containers display their elements with repr. Truth testing calls __bool__, then falls back to nonzero __len__, and otherwise treats instances as true. Keep these methods inexpensive and free from consequential side effects.
class Batch:
def __init__(self, name, jobs):
self.name = name
self.jobs = list(jobs)
def __repr__(self):
return f'Batch({self.name!r}, {self.jobs!r})'
def __str__(self):
return f'{self.name}: {len(self.jobs)} jobs'
def __bool__(self):
return bool(self.jobs)
batch = Batch('nightly', ['build', 'test'])
print(repr(batch))
print(batch)
print(bool(batch), bool(Batch('empty', []))) Batch('nightly', ['build', 'test'])
nightly: 2 jobs
True FalseKeep equality and hashing consistent
Equality should compare the semantic state that defines a value and return NotImplemented for unrelated types rather than raising or returning an arbitrary false result. Objects that compare equal must have equal hashes. Mutable value objects normally remain unhashable because changing key fields after insertion corrupts dictionary and set assumptions; defining __eq__ without an appropriate __hash__ makes instances unhashable automatically.
class Coordinate:
__slots__ = ('_x', '_y')
def __init__(self, x, y):
object.__setattr__(self, '_x', x)
object.__setattr__(self, '_y', y)
def __setattr__(self, name, value):
raise AttributeError('Coordinate is immutable')
def __eq__(self, other):
if not isinstance(other, Coordinate):
return NotImplemented
return (self._x, self._y) == (other._x, other._y)
def __hash__(self):
return hash((self._x, self._y))
first = Coordinate(2, 5)
second = Coordinate(2, 5)
lookup = {first: 'station'}
print(first == second)
print(lookup[second])
print(first == (2, 5)) True
station
FalseReturn values or NotImplemented from operators
Rich comparisons and arithmetic use a negotiation between operands. Return NotImplemented when the operand type is unsupported so Python can try its reflected method; raising NotImplemented is an error because it is a singleton value, not an exception. Ordering need not follow equality automatically. Define a clear domain order, or use functools.total_ordering when its convenience outweighs slower, deeper generated comparisons.
from functools import total_ordering
@total_ordering
class Score:
def __init__(self, points): self.points = points
def __eq__(self, other):
return self.points == other.points if isinstance(other, Score) else NotImplemented
def __lt__(self, other):
return self.points < other.points if isinstance(other, Score) else NotImplemented
def __add__(self, other):
return Score(self.points + other.points) if isinstance(other, Score) else NotImplemented
def __radd__(self, other):
return Score(other + self.points) if isinstance(other, int) else NotImplemented
def __repr__(self): return f'Score({self.points})'
print(sorted([Score(8), Score(3), Score(5)]))
print(Score(2) + Score(4))
print(10 + Score(5)) [Score(3), Score(5), Score(8)]
Score(6)
Score(15)Implement only the container capabilities you promise
__len__, __iter__, __contains__, and __getitem__ are separate capabilities even though Python has fallbacks between some of them. Explicit iteration and membership make behavior and performance clearer. __getitem__ receives slice objects as well as scalar keys, so delegating to an underlying sequence conveniently preserves both. Mutation methods should be omitted when the abstraction is read-only.
class Window:
def __init__(self, values): self._values = tuple(values)
def __len__(self): return len(self._values)
def __iter__(self): return iter(self._values)
def __getitem__(self, key): return self._values[key]
def __contains__(self, value): return value in self._values
window = Window([4, 6, 8, 10])
print(len(window), 6 in window)
print(list(window))
print(window[1:3]) 4 True
[4, 6, 8, 10]
(6, 8)Use properties for focused attribute invariants
property is a descriptor that keeps familiar attribute syntax while executing getter, setter, or deleter logic. It is appropriate for validation or a computed compatibility layer, but surprising I/O behind ordinary access is difficult to reason about. Store the underlying state under a different name to avoid recursively invoking the property.
class Percentage:
def __init__(self, value):
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, value):
if not 0 <= value <= 100:
raise ValueError('outside 0..100')
self._value = value
progress = Percentage(40)
progress.value += 15
print(progress.value)
try:
progress.value = 120
except ValueError as error:
print(error) 55
outside 0..100Prefer __getattr__ for narrow lookup fallback
__getattr__ runs only when normal lookup raises AttributeError, making it safer for computed aliases or delegation. __getattribute__ intercepts every instance lookup and must call object.__getattribute__ or another base implementation to avoid infinite recursion. Missing names must ultimately raise AttributeError so hasattr, getattr defaults, introspection, and debugging retain their expected semantics.
class Record:
def __init__(self, **fields):
self.fields = fields
def __getattr__(self, name):
try:
return self.fields[name]
except KeyError:
raise AttributeError(name) from None
record = Record(owner='Ada', status='ready')
print(record.owner)
print(record.fields['status'])
print(getattr(record, 'missing', 'unknown'))
print(hasattr(record, 'missing')) Ada
ready
unknown
FalseReuse managed fields with descriptors
A descriptor defines __get__, __set__, or __delete__ and controls attribute access when stored on a class. A descriptor defining __set__ is a data descriptor and takes precedence over an instance dictionary entry. __set_name__, called during class creation, lets one descriptor discover its storage name; return self when __get__ receives instance=None so class-level inspection remains useful.
class Positive:
def __set_name__(self, owner, name):
self.storage_name = '_' + name
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if value <= 0:
raise ValueError('must be positive')
setattr(instance, self.storage_name, value)
class Rectangle:
width = Positive()
height = Positive()
def __init__(self, width, height):
self.width, self.height = width, height
shape = Rectangle(3, 4)
print(shape.width * shape.height)
print(Rectangle.width.storage_name) 12
_widthLocal code tester
Build a protocol-aware value collection
Experiment with representation, equality, iteration, membership, slicing, and truth testing on one small class.
Press Run to load Python locally.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



