Extra Topic: Special Methods
Note: extra topics are not in CS Academy, but are required
and can appear on any future
homework, writing session, quiz, or exam.
Getting Started
-
Special methods are the official name, but
these are also called magic methods and dunder methods
(dunder is short for "double-underscore").
-
These methods have double-underscores in their names, like
__init__.
-
Python uses these methods to extend first-class behaviors to custom classes.
For example,
hash(value) is a global function that calls
the value.__hash__() special method.
-
These are the special methods in our core curriculum:
-
__init__(self)
The contructor for instances of this class. At a minimum,
this initializes the fields (also called properties or attributes)
of the instance, but it can do more than that.
-
__str__(self)
This converts the instance to a human-readable string. A call
to str(value) is converted by Python to value.__str__().
Python calls this automatically when you call print(value).
-
__repr__(self)
In theory, this is supposed to convert the instance to a Python-readble string,
so that eval(repr(value)) should create a copy of the value.
However, at least in 112, we do not use it this way. Instead, we use it
instead of __str__ because Python uses repr and not str
when converting values in lists, tuples, etc.
-
__eq__(self, other)
This returns True if self is equal to other.
Though slightly more complicated, Python basically converts
x == y into x.__eq__(y).
If you do not write this special method, then the (undesirable)
default behavior is that two values are equal only if
they are aliases (ugh).
-
__hash__(self)
This returns the hash value (an integer) for the instance.
Python converts hash(value) to value.__hash__().
You must define this method (along with __eq__)
if you want to use instances in sets or as keys in dicts.
A not-very-efficient but good-enough-for-112 way to do this
is to simply return hash(str(self)). This converts
the instance to a string, then hashes that string.
Example: A basic Vector class:
class Vector:
def __init__(self, dx, dy):
self.dx = dx
self.dy = dy
def __repr__(self):
return f'Vector({self.dx}, {self.dy})'
def __eq__(self, other):
return (isinstance(other, Vector) and
self.dx == other.dx and
self.dy == other.dy)
def __hash__(self):
return hash((self.dx, self.dy))
v1 = Vector(2, 3)
print(v1) # Vector(2, 3)
print(v1 == Vector(2, 3)) # True
print(v1 == Vector(2, 4)) # False
Making + work with Vector instances (using __add__):
class Vector:
def __init__(self, dx, dy):
self.dx = dx
self.dy = dy
def __repr__(self):
return f'Vector({self.dx}, {self.dy})'
def __eq__(self, other):
return (isinstance(other, Vector) and
self.dx == other.dx and
self.dy == other.dy)
def __hash__(self):
return hash((self.dx, self.dy))
def __add__(self, other):
assert(isinstance(other, Vector))
return Vector(self.dx + other.dx,
self.dy + other.dy)
v1 = Vector(2, 3)
v2 = Vector(2, 4)
v3 = v1 + v2
print(v3) # Vector(4, 7)
Making < work with Vector instances (using __lt__):
class Vector:
def __init__(self, dx, dy):
self.dx = dx
self.dy = dy
def __repr__(self):
return f'Vector({self.dx}, {self.dy})'
def __eq__(self, other):
return (isinstance(other, Vector) and
self.dx == other.dx and
self.dy == other.dy)
def __hash__(self):
return hash((self.dx, self.dy))
def __add__(self, other):
assert(isinstance(other, Vector))
return Vector(self.dx + other.dx,
self.dy + other.dy)
def __lt__(self, other):
assert(isinstance(other, Vector))
return self.magnitude() < other.magnitude()
def magnitude(self):
return (self.dx**2 + self.dy**2)**0.5
v1 = Vector(2, 3)
v2 = Vector(2, 4)
print(v1 < v2) # True
print(v2 < v1) # False
print(Vector(3, 4) < Vector(4, 3)) # False
print(Vector(4, 3) < Vector(3, 4)) # False