Special methods
Special methods have double underscores on both sides of their name, which is why they are called dunder methods. Python calls them for you in response to built in operations: printing, adding, comparing, indexing, iterating. Defining them makes your own classes behave like built in types.
str and repr
__str__ is the friendly version shown by print(). __repr__ is the developer version shown in the shell and inside collections, and should ideally look like the code that would recreate the object.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(2, 3)
print(p)
print([p, Point(0, 0)])If you only define one, define __repr__; print falls back to it.
eq and ordering
By default two objects are equal only if they are the same object. Define __eq__ to compare by value.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x, self.y) < (other.x, other.y)
print(Point(1, 2) == Point(1, 2))
print(sorted([Point(3, 1), Point(1, 5)], key=lambda p: (p.x, p.y))[0].x)With __lt__ defined you can sort a list of points directly. functools.total_ordering fills in the remaining comparison methods from __eq__ and one other.
Arithmetic operators
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, k):
return Vector(self.x * k, self.y * k)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(1, 2) + Vector(3, 4)
print(v)
print(v * 3)| Operator | Method |
|---|---|
+ | __add__ |
- | __sub__ |
* | __mul__ |
/ | __truediv__ |
// | __floordiv__ |
% | __mod__ |
** | __pow__ |
==, != | __eq__, __ne__ |
<, <=, >, >= | __lt__, __le__, __gt__, __ge__ |
len, getitem and contains
These make an object work with len(), indexing and in.
class Playlist:
def __init__(self, songs):
self.songs = list(songs)
def __len__(self):
return len(self.songs)
def __getitem__(self, i):
return self.songs[i]
def __contains__(self, song):
return song in self.songs
pl = Playlist(["Intro", "Verse", "Chorus"])
print(len(pl))
print(pl[1])
print("Chorus" in pl)
for s in pl: # __getitem__ alone is enough to make it iterable
print(s)call
Makes an instance callable like a function.
class Multiplier:
def __init__(self, k):
self.k = k
def __call__(self, x):
return x * self.k
triple = Multiplier(3)
print(triple(10))enter and exit
These power the with statement. See the file handling lesson for the usual case, but you can write your own:
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
import time
print(f"took {time.perf_counter() - self.start:.4f}s")
with Timer():
sum(range(100000))bool and hash
__bool__ decides truthiness. __hash__ is needed if objects with __eq__ are used as dictionary keys or set members. Dataclasses can generate both, which is usually the easiest route.
Practice
- Give the
Vectorclass__sub__and__eq__, then check thatVector(3, 3) - Vector(1, 1) == Vector(2, 2). - Write a
Deckclass that holds cards and supportslen(deck),deck[0]andfor card in deck. - Write a
Moneyclass whose__str__prints an amount likeRs 1,250.00.
