Skip to content

Commit e06ac86

Browse files
authored
Merge pull request animator#779 from Jaya-Prakash-17/main
Content Added: Protocols
2 parents 15fed7e + 7b07a5a commit e06ac86

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

contrib/advanced-python/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
- [Regular Expressions in Python](regular_expressions.md)
88
- [JSON module](json-module.md)
99
- [Map Function](map-function.md)
10+
- [Protocols](protocols.md)
1011
- [Exception Handling in Python](exception-handling.md)

contrib/advanced-python/protocols.md

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Protocols in Python
2+
Python can establish informal interfaces using protocols In order to improve code structure, reusability, and type checking. Protocols allow for progressive adoption and are more flexible than standard interfaces in other programming languages like JAVA, which are tight contracts that specify the methods and attributes a class must implement.
3+
4+
>Before going into depth of this topic let's understand another topic which is pre-requisite od this topic \#TypingModule
5+
6+
## Typing Module
7+
This is a module in python which provides
8+
1. Provides classes, functions, and type aliases.
9+
2. Allows adding type annotations to our code.
10+
3. Enhances code readability.
11+
4. Helps in catching errors early.
12+
13+
### Type Hints in Python:
14+
Type hints allow you to specify the expected data types of variables, function parameters, and return values. This can improve code readability and help with debugging.
15+
16+
Here is a simple function that adds two numbers:
17+
```python
18+
def add(a,b):
19+
return a + b
20+
add(10,20)
21+
```
22+
>Output: 30
23+
24+
While this works fine, adding type hints makes the code more understandable and serves as documentation:
25+
26+
```python
27+
def add(a:int, b:int)->int:
28+
return a + b
29+
print(add(1,10))
30+
```
31+
>Output: 11
32+
33+
In this version, `a` and `b` are expected to be integers, and the function is expected to return an integer. This makes the function's purpose and usage clearer.
34+
35+
#### let's see another example
36+
37+
The function given below takes an iterable (it can be any off list, tuple, dict, set, frozeset, String... etc) and print it's content in a single line along with it's type.
38+
39+
```python
40+
from typing import Iterable
41+
# type alias
42+
43+
def print_all(l: Iterable)->None:
44+
print(type(l),end=' ')
45+
for i in l:
46+
print(i,end=' ')
47+
print()
48+
49+
l = [1,2,3,4,5] # type: List[int]
50+
s = {1,2,3,4,5} # type: Set[int]
51+
t = (1,2,3,4,5) # type: Tuple[int]
52+
53+
for iter_obj in [l,s,t]:
54+
print_all(iter_obj)
55+
56+
```
57+
Output:
58+
> <class 'list'> 1 2 3 4 5
59+
> <class 'set'> 1 2 3 4 5
60+
> <class 'tuple'> 1 2 3 4 5
61+
62+
and now lets try calling the function `print_all` using a non-iterable object `int` as argument.
63+
64+
```python
65+
a = 10
66+
print_all(a) # This will raise an error
67+
```
68+
Output:
69+
>TypeError: 'int' object is not iterable
70+
71+
This error occurs because `a` is an `integer`, and the `integer` class does not have any methods or attributes that make it work like an iterable. In other words, the integer class does not conform to the `Iterable` protocol.
72+
73+
**Benefits of Type Hints**
74+
Using type hints helps in several ways:
75+
76+
1. **Error Detection**: Tools like mypy can catch type-related problems during development, decreasing runtime errors.
77+
2. **Code Readability**: Type hints serve as documentation, making it easy to comprehend what data types are anticipated and returned.
78+
3. **Improved Maintenance**: With unambiguous type expectations, maintaining and updating code becomes easier, especially in huge codebases.
79+
80+
Now that we have understood about type hints and typing module let's dive deep into protocols.
81+
82+
## Understanding Protocols
83+
84+
In Python, protocols define interfaces similar to Java interfaces. They let you specify methods and attributes that an object must implement without requiring inheritance from a base class. Protocols are part of the `typing` module and provide a way to enforce certain structures in your classes, enhancing type safety and code clarity.
85+
86+
### What is a Protocol?
87+
88+
A protocol specifies one or more method signatures that a class must implement to be considered as conforming to the protocol.
89+
This concept is often referred to as "structural subtyping" or "duck typing," meaning that if an object implements the required methods and attributes, it can be treated as an instance of the protocol.
90+
91+
Let's write our own protocol:
92+
93+
```python
94+
from typing import Protocol
95+
96+
# Define a Printable protocol
97+
class Printable(Protocol):
98+
def print(self) -> None:
99+
"""Print the object"""
100+
pass
101+
102+
# Book class implements the Printable protocol
103+
class Book:
104+
def __init__(self, title: str):
105+
self.title = title
106+
107+
def print(self) -> None:
108+
print(f"Book Title: {self.title}")
109+
110+
# print_object function takes a Printable object and calls its print method
111+
def print_object(obj: Printable) -> None:
112+
obj.print()
113+
114+
book = Book("Python Programming")
115+
print_object(book)
116+
```
117+
Output:
118+
> Book Title: Python Programming
119+
120+
In this example:
121+
122+
1. **Printable Protocol:** Defines an interface with a single method print.
123+
2. **Book Class:** Implements the Printable protocol by providing a print method.
124+
3. **print_object Function:** Accepts any object that conforms to the Printable protocol and calls its print method.
125+
126+
we got our output because the class `Book` confirms to the protocols `printable`.
127+
similarly When you pass an object to `print_object` that does not conform to the Printable protocol, an error will occur. This is because the object does not implement the required `print` method.
128+
Let's see an example:
129+
```python
130+
class Team:
131+
def huddle(self) -> None:
132+
print("Team Huddle")
133+
134+
c = Team()
135+
print_object(c) # This will raise an error
136+
```
137+
Output:
138+
>AttributeError: 'Team' object has no attribute 'print'
139+
140+
In this case:
141+
- The `Team` class has a `huddle` method but does not have a `print` method.
142+
- When `print_object` tries to call the `print` method on a `Team` instance, it raises an `AttributeError`.
143+
144+
> This is an important aspect of using protocols: they ensure that objects provide the necessary methods, leading to more predictable and reliable code.
145+
146+
**Ensuring Protocol Conformance**
147+
To avoid such errors, you need to ensure that any object passed to `print_object` implements the `Printable` protocol. Here's how you can modify the `Team` class to conform to the protocol:
148+
```python
149+
class Team:
150+
def __init__(self, name: str):
151+
self.name = name
152+
153+
def huddle(self) -> None:
154+
print("Team Huddle")
155+
156+
def print(self) -> None:
157+
print(f"Team Name: {self.name}")
158+
159+
c = Team("Dream Team")
160+
print_object(c)
161+
```
162+
Output:
163+
>Team Name: Dream Team
164+
165+
The `Team` class now implements the `print` method, conforming to the `Printable` protocol. and hence, no longer raises an error.
166+
167+
### Protocols and Inheritance:
168+
Protocols can also be used in combination with inheritance to create more complex interfaces.
169+
we can do that by following these steps:
170+
**Step 1 - Base protocol**: Define a base protocol that specifies a common set of methods and attributes.
171+
**Step 2 - Derived Protocols**: Create derives protocols that extends the base protocol with addition requirements
172+
**Step 3 - Polymorphism**: Objects can then conform to multiple protocols, allowing for Polymorphic behavior.
173+
174+
Let's see an example on this as well:
175+
176+
```python
177+
from typing import Protocol
178+
179+
# Base Protocols
180+
class Printable(Protocol):
181+
def print(self) -> None:
182+
"""Print the object"""
183+
pass
184+
185+
# Base Protocols-2
186+
class Serializable(Protocol):
187+
def serialize(self) -> str:
188+
pass
189+
190+
# Derived Protocol
191+
class PrintableAndSerializable(Printable, Serializable):
192+
pass
193+
194+
# class with implementation of both Printable and Serializable
195+
class Book_serialize:
196+
def __init__(self, title: str):
197+
self.title = title
198+
199+
def print(self) -> None:
200+
print(f"Book Title: {self.title}")
201+
202+
def serialize(self) -> None:
203+
print(f"serialize: {self.title}")
204+
205+
# function accepts the object which implements PrintableAndSerializable
206+
def test(obj: PrintableAndSerializable):
207+
obj.print()
208+
obj.serialize()
209+
210+
book = Book_serialize("lean-in")
211+
test(book)
212+
```
213+
Output:
214+
> Book Title: lean-in
215+
serialize: lean-in
216+
217+
In this example:
218+
219+
**Printable Protocol:** Specifies a `print` method.
220+
**Serializable Protocol:** Specifies a `serialize` method.
221+
**PrintableAndSerializable Protocol:** Combines both `Printable` and `Serializable`.
222+
**Book Class**: Implements both `print` and `serialize` methods, conforming to `PrintableAndSerializable`.
223+
**test Function:** Accepts any object that implements the `PrintableAndSerializable` protocol.
224+
225+
If you try to pass an object that does not conform to the `PrintableAndSerializable` protocol to the test function, it will raise an `error`. Let's see an example:
226+
227+
```python
228+
class Team:
229+
def huddle(self) -> None:
230+
print("Team Huddle")
231+
232+
c = Team()
233+
test(c) # This will raise an error
234+
```
235+
output:
236+
> AttributeError: 'Team' object has no attribute 'print'
237+
238+
In this case:
239+
The `Team` class has a `huddle` method but does not implement `print` or `serialize` methods.
240+
When test tries to call `print` and `serialize` on a `Team` instance, it raises an `AttributeError`.
241+
242+
**In Conclusion:**
243+
>Python protocols offer a versatile and powerful means of defining interfaces, encouraging the decoupling of code, improving readability, and facilitating static type checking. They are particularly handy for scenarios involving file-like objects, bespoke containers, and any case where you wish to enforce certain behaviors without requiring inheritance from a specific base class. Ensuring that classes conform to protocols reduces runtime problems and makes your code more robust and maintainable.

0 commit comments

Comments
 (0)