2017-07-17 15:20:00 +0000
Python 3, tutorial
This post summarizes the Pythonic concept of interface, mainly by taking notes from the chapter 11 of the book “Fluent Python”.
In the context of Object Oriented Programming, a protocol is an informal interface, defined only in documentation and not in code. For example, the sequence protocol in Python entails just the
__len__
and__getitem__
methods. Any classSpam
that implements those methods with the standard signature and semantics can be used anywhere a sequence is expected. WhetherSpam
is a subclass of this or that is irrelevant, all that matters is that it provides the necessary methods.
The following example shows how the sequence protocol works. SequenceLikeClass
implements the sequence protocol by duck typing: to equip with special methods __len__
and __getitem__
, which are methods corresponding to the sequence protocol.
class SequenceLikeClass:
def __init__(self, items):
self.sequence = list(items)
def __len__(self):
return len(self.sequence)
def __getitem__(self, position):
return self.sequence[position]
Although it does not inherit from sequence class like list, it behaves well like some sequence class.
# instantiate
>>> seq = SequenceLikeClass(range(5))
# len works
>>> len(seq)
5
# getting an item through index
>>> seq[2]
2
# negative index supported
>>> seq[-1]
4
# slicing works
>>> seq[2:4]
[2, 3]
# for loop works even if it does not have __iter__ method
>>> for j in seq: print(j)
0
1
2
3
4
# in operator works even if it does not have __contains__
>>> 1 in seq
True
# choice requires sequence as an argument, it naturally works
>>> from random import choice
>>> choice(seq)
1 # randomly chosen among 0, 1, 2, 3, and 4
ABCs makes protocols explicit.
Abstract base classes complement duck-typing by providing a way to define interfaces when other techniques like hasattr() would be clumsy or subtly wrong (for example with magic methods). ABCs introduce virtual subclasses, which are classes that don’t inherit from a class but are still recognized by isinstance() and issubclass()
What goose typing means is:
isinstance(obj, cls)
is now just fine… as long ascls
is an Abstract Base Class.
from collections.abc import Sized # Sized needs __len__
class SizedClass(Sized):
def __init__(self):
print('hello')
>>> sc = SizedClass() # without __len__, Python gives us an error when instantiating, not importing
# TypeError: Can't instantiate abstract class SizedClass with abstract methods __len__
class RealSizedClass(Sized): # let's redefine
def __len__(self):
return 2
>>> rsc = RealSizedClass()
>>> isinstance(rsc, Sized)
True # of course
@Sized.register # register decorator allows us to do goose typing
class BearSizedClass: # merely an object with __len__ method
def __len__(self):
return 2
>>> bsc = BearSizedClass()
>>> isinstance(bsc, Sized)
True # actually, it is also true without the decorator...
# why? check the book. (abc.Sized has __subclasshook__ implemented)