In Python everything is an
An object is formed by:
- A memory location identified internally by an
- A type (class) that determines the protocols of the object.
- A value (or a set of values) that the object holds.
# Given an object identified by the variable name `a` >>> a = 1 # We can access its memory location `id` >>> id(a) 1407854654624 # We can access its type >>> type(a) '<class 'int'>' # We can access its value >>> a 1 # We can use the behavior defined on its protocol >>> a + 1 2 >>> a.hex() '0x1'
The above will be similar for every object in Python.
Every object is based on a type, a type is a
[0:5]<- is a
str.upper<- is a
Function String Integer Slice __⬆️__ __⬆️_ _⬆️_ __⬆️__ print("Hello" + "World!"[0:5].upper()) ___⬆️___ __⬆️___ Symbol Method Descriptor
Python is a POP Language (Protocol Oriented Programming)
The type of the object determines its implementation, which exposes the behavior, the behavior are the things that the object can do or things that can be done with the object.
There are languages that calls it Traits of an object.
Each set of those abilities is what we call a Protocol, protocols are useful for setting contracts 🤝 between objects.
Identifying protocols on the Hello World program:
Callable Subscriptable Sliceable __⬆️__ ______⬆️ __________⬆️ print("Hello" + "World!"[0:5].upper()) ________⬆️________ __⬆️__ Summable Callable
Callable 🗣️ Can be invoked using ()
A type is also callable when its protocol includes the
Subscriptable ✍🏻 its elements can be accessed through a subscription.
The subscription can be numeric ordinal
or named key
['name']. A type is Subscriptable when its protocol includes the
Sliceable 🔪 its collection of elements can be sliced in parts.
A type is Sliceable when it is Subscriptable and its
__getitem__method can accept a
slicein place of the
name. A slice is the composition of
Summable ➕ Can be combined with other objects via
The product of this combination is always new object. On numeric types this is the ability to
sumtwo or more numbers in a set. On sequences it is the
concatenationof its fragments in to one. A type is Summable when its protocol includes the
Printable 🖨️ Can be printed using
All the Python objects are printable,
__str__method for printing the object.
ℹ️ There are many more and in fact, you can define custom protocols, Protocols are very generic and there is no official list of protocols although there are some pre-defined protocols in the typing module.
from typing import Iterator, Iterable, Optional, Sequence, Awaitable, Generic
Complete list of protocols and sub typing is available on https://mypy.readthedocs.io/en/stable/protocols.html
🦆 Protocols empowers an approach called Duck Typing which is the fact that in Python if an object looks like, behaves like, and has the behavior of a Duck, it is said to be a Duck, regardless if this is the case of a Dog that learned to say quack immitating a Duck 🐶.
Some protocols can be checked using built-in functions
callable(print) is True callable("Hello") is False
Some protocols must be checked against its type class
isinstance("Hello", str) is True isinstance(0, slice) is False
There are cases where the only way to verify protocols
is checking for its attributes.
hasattr("Hello", "__add__") is True # Summable, we can use `+` operator.
Others where we need to use the EAFP pattern.
try: "Hello" + 1 except TypeError: # Strong type checking # we cannot `__add__` an `str` to an `int`
Python3 offers a way to define custom protocols
from typing import Protocol, runtime_checkable @runtime_checkable class CallableSummableSubscriptable(Protocol): def __call__(self) -> T: ... def __add__(self, other: T) -> T: ... def __getitem__(self, item: T) -> T: ...
ℹ️ Protocol methods are just signatures with empty bodies, stated by the
Tis usually a type alias indicating a generic type.
Protocols are useful to define contracts, bounds on function signatures for example defining a function that accepts an argument only if the type has the specified protocol.
def awesome_function(thing: CallableSummableSubscriptable): # accepts only objects that implements that ⬆️ protocol. # Protocols can be checked at runtime @runtime_checkable # Or checked using static analysers e.g: mypy
OOP Python is actually POP (Protocol Oriented Programming)
- More about Protocols and Behavior.
- Less about tradicional OOP concepts.
All the traditional concepts and patterns are also available, but some are intrinsic to objects and protocols that in the end of the day the programmers doesn't have to take care of it at all in the same way.
The ability to inherit from another base type and take all its behavior and the ability to override with custom implementation.
class MyString(str) def __str__(self): return super().__str__().upper() >>> x = MyString("Bruno") >>> print(x) "BRUNO"
The ability to hide object attributes and methods and expose only a selected set of them or to expose them in a more controlled way.
# Descriptor is a protocol for getter/setter like approach. class Field: def __get__(...): def __set__(...): class Thing: # Double underline means that the field is private # but actually it is only a naming mangling convention. __protected_attr = Field() # Properties can also be used to define getter/setter @property def foo(self): return self.__protected_attr @foo.setter def set_foo(self, value): self.__protected_attr = value
The ability for objects to behave differently regardless of its base type and for procedures to take different types of objects as arguments.
len("Bruno") len([1, 2, 3]) dict.get("key") dict.get("key", default="other") print("Hello") print(123) print(*["Hello", "World"]) def function(*args, **kwargs): ...
ℹ️ In traditional OOP literature polymorphism is often used to define only the ability to have methods reusing the same name but different implementation, but in fact it goes deeper than this.
🧑🍳 A Chef when selecting ingredients for a recipe, will look for the protocols defined on each ingredient, the Chef can go to the supermarket and see a set of different types of onions 🧅, even if the recipe only says onion the Chef knows based on the desired behavior that wants a specific type of onion, white onions are swetter, better for cooked recipes, the purple onions are more acid so better for salads and raw recipes. (but that also depends a bit on taste)
🧑💻 The Software Engineer when choosing the data structures to use in an algorithm 🤖 must look to the protocols defined and how the objects behaves even if the requirements says it is a collection of texts for example, the engineer must analyse the program to choose wisely between a tuple, a list or even a set.
The protocols are often more important than the types.
NOTE: Credits to @mathsppblog to have inspired the first paragraph of this post https://twitter.com/mathsppblog/status/1445148609977126914