Special Methods & Protocols¶
Special methods (also called magic methods) are the secret sauce to Python’s Duck typing.
Defining the appropriate special methods in your classes is how you make your class act like standard classes.
What’s in a Name?¶
We’ve seen at least one special method so far:
__init__
It’s all in the double underscores...
Pronounced “dunder” (or “under-under”)
try: dir(2)
or dir(list)
Generally Useful Special Methods¶
Most classes should at least have these special methods:
object.__str__
:- Called by the str() built-in function and by the print function to compute the informal string representation of an object.
object.__repr__
:Called by the repr() built-in function to compute the official string representation of an object.
(ideally:
eval( repr(something) ) == something
)
Protocols¶
The set of special methods needed to emulate a particular type of Python object is called a protocol.
Your classes can “become” like Python built-in classes by implementing the methods in a given protocol.
Remember, these are more guidelines than laws. Implement what you need.
The Numerics Protocol¶
Do you want your class to behave like a number? Implement these methods:
object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(self, other)
object.__floordiv__(self, other)
object.__mod__(self, other)
object.__divmod__(self, other)
object.__pow__(self, other[, modulo])
object.__lshift__(self, other)
object.__rshift__(self, other)
object.__and__(self, other)
object.__xor__(self, other)
object.__or__(self, other)
The Container Protocol¶
Want to make a container type? Here’s what you need:
object.__len__(self)
object.__getitem__(self, key)
object.__setitem__(self, key, value)
object.__delitem__(self, key)
object.__iter__(self)
object.__reversed__(self)
object.__contains__(self, item)
object.__getslice__(self, i, j)
object.__setslice__(self, i, j, sequence)
object.__delslice__(self, i, j)
An Example¶
Each of these methods supports a common Python operation.
For example, to make ‘+’ work with a sequence type in a vector-like fashion,
implement __add__
:
def __add__(self, v):
"""return the element-wise vector sum of self and v
"""
assert len(self) == len(v)
return vector([x1 + x2 for x1, x2 in zip(self, v)])
[a more complete example may be seen here
]
Protocols in Summary¶
Use special methods when you want your class to act like a “standard” class in some way.
Look up the special methods you need and define them.
There’s more to read about the details of implementing these methods:
Emulating Standard types¶
Making your classes behave like the built-ins
Callable classes¶
We’ve been using functions a lot:
def my_fun(something):
do_something
...
return something
And then we can call it:
result = my_fun(some_arguments)
But what if we need to store some data to know how to evaluate that function?
Example: a function that computes a quadratic function:
You could pass in a, b and c each time:
def quadratic(x, a, b, c):
return a * x**2 + b * x + c
But what if you are using the same a, b, and c numerous times?
Or what if you need to pass this in to something (like map) that requires a function that takes a single argument?
“Callables”¶
Various places in python expect a “callable” – something that you can call like a function:
a_result = something(some_arguments)
“something” in this case is often a function, but can be anything else that is “callable”.
What have we been introduced to recently that is “callable”, but not a function object?
Custom callable objects¶
The trick is one of Python’s “magic methods”
__call__(*args, **kwargs)
If you define a __call__
method in your class, it will be used when
code “calls” an instance of your class:
class Callable:
def __init__(self, .....)
some_initilization
def __call__(self, some_parameters)
Then you can do:
callable_instance = Callable(some_arguments)
result = callable_instance(some_arguments)
Writing your own sequence type¶
Python has a handful of nifty sequence types built in:
- lists
- tuples
- strings
- ...
But what if you need a sequence that isn’t built in?
A Sparse array¶
Example: Sparse Array
Sometimes we have data sets that are “sparse” – i.e. most of the values are zero.
So you may not want to store a huge bunch of zeros.
But you do want the array to look like a regular old sequence.
So how do you do that?
The Sequence protocol¶
You can make your class look like a regular python sequence by defining the set of special methods you need:
https://docs.python.org/3/reference/datamodel.html#emulating-container-types
The key ones are:
__len__ |
for len(sequence) |
__getitem__ |
for x = seq[i] |
__setitem__ |
for seq[i] = x |
__delitem__ |
for del seq[i] |
__contains__ |
for x in seq |