Python Protocol Oriented Programming
Tags: python, oop, programming, beginners
Published: 2021-11-05 21:20:00

## Python Objects 🐍

In Python everything is an `object`.

An object is formed by:

- A **memory location** identified internally by an `id`.
- A **type (class)** that determines the protocols of the object.
- A **value** (or a set of values) that the object holds.

Example:

```py
# 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.

## Types 🔠

Every object is based on a type, a type is a `class` definition.

- `print` <- is `function` type
- `"Hello"` <- is `str` type
- `0` <- is `int` type
- `[0:5]` <- is a `slice` type
- `str.upper` <- is a `method_descriptor` type

```py
 Function String Integer       Slice
    __⬆️__  __⬆️_ _⬆️_         __⬆️__
    print("Hello"[0] + "World!"[0:5].upper())
                  ___⬆️___            __⬆️___
                   Symbol            Method Descriptor
```

## Protocols 📝

> 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:

```py
   Callable  Subscriptable   Sliceable
    __⬆️__  ______⬆️    __________⬆️
    print("Hello"[0] + "World!"[0:5].upper())
             ________⬆️________      __⬆️__
                  Summable         Callable
```

- **Callable** 🗣️ Can be invoked using ()
  A type is also callable when its protocol includes the `__call__` method.

- **Subscriptable**  ✍🏻 its elements can be accessed through a subscription. 
  The subscription can be numeric ordinal `[0]` or named key `['name']`. A type is Subscriptable when its protocol includes the `__getitem__` method.

- **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 `slice` in place of the `index` or `name`. A slice is the composition of `[start:stop:step]` elements.

- **Summable** ➕ Can be combined with other objects via `+` operation.
  The product of this combination is always new object. On numeric types this is the ability to `sum` two or more numbers in a set. On sequences it is the `concatenation` of its fragments in to one. A type is Summable when its protocol includes the `__add__` or `__radd__` method.

- **Printable** 🖨️ Can be printed using `print`
  All the Python objects are printable, `print` will look either for a `__repr__` or a `__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.

```py
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](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** 🐶.

## Typing and Protocol Checking

Some protocols can be checked using built-in functions

```py
callable(print) is True
callable("Hello") is False
```

Some protocols must be checked against its **type class**

```py
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.

```py
hasattr("Hello", "__add__") is True  # Summable, we can use `+` operator.
```

Others where we need to use the **EAFP** pattern.

```py
try: 
    "Hello" + 1 
except TypeError: # Strong type checking
    # we cannot `__add__` an `str` to an `int`
```

## Typing Protocols

Python3 offers a way to define custom protocols

```py
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 `...`. `T` is 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.

```py
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
```

## Is there the Traditional OOP in Python?

**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.

### Inheritance

The ability to **inherit** from another base type and take all its behavior and the ability to **override** with custom implementation.

```py
class MyString(str)
    def __str__(self):
        return super().__str__().upper()

>>> x = MyString("Bruno")
>>> print(x)
"BRUNO"
```

### Encapsulation

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.

```py
# 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
```

### Polymorfism

The ability for objects to behave differently regardless of its base type and for procedures to take different types of objects as arguments.

```py
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.

## Conclusion

🧑‍🍳 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