Skip to content

Commit 729e861

Browse files
lpozoclaudebzaczynski
authored
Sample code for: Implementing Interfaces in Python: ABCs and Protocols (#758)
* Sample code for: Implementing Interfaces in Python: ABCs and Protocols * Sync python-interface materials with tutorial update - Fix typo in check_protocols.py import (readers_protocols -> readers_protocol). - Import runtime_checkable in readers_protocol.py so uncommenting the decorator doesn't NameError. - Remove meta_classes.py and check_virtual_classes.py: the metaclass-based virtual subclass example was dropped from the updated tutorial in favor of typing.Protocol + @runtime_checkable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Enable @runtime_checkable in readers_protocol.py Matches the tutorial's final state (highlighted block in the Runtime Type-Checking Protocol Subtypes section) and silences the F401 ruff error for the unused runtime_checkable import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Final QA --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Bartosz Zaczyński <bartosz.zaczynski@gmail.com>
1 parent 4ef5468 commit 729e861

8 files changed

Lines changed: 152 additions & 0 deletions

File tree

python-interface/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Implementing Interfaces in Python: ABCs and Protocols
2+
3+
This folder provides the code examples for the Real Python tutorial [Implementing Interfaces in Python: ABCs and Protocols](https://realpython.com/python-interface/)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from readers_protocol import EmailReader, FileReaderProtocol, PdfReader
2+
3+
4+
def read(reader: FileReaderProtocol, path: str) -> str:
5+
reader.load_file(path)
6+
return reader.extract_text()
7+
8+
9+
# Accepted by the type checker
10+
read(PdfReader(), "/reports/report.pdf")
11+
read(EmailReader(), "/mail/message.eml")

python-interface/create_readers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from readers_abc import EmailReader, FileReaderInterface, PdfReader
2+
3+
pdf_reader = PdfReader()
4+
email_reader = EmailReader()
5+
6+
print(isinstance(PdfReader(), FileReaderInterface))
7+
print(issubclass(PdfReader, FileReaderInterface))

python-interface/readers.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class PdfReader:
2+
"""Extract text from a PDF."""
3+
4+
def load_file(self, path: str) -> None:
5+
print(f"Loading PDF from {path}")
6+
7+
def extract_text(self) -> str:
8+
return "Extracted PDF text"
9+
10+
11+
class EmailReader:
12+
"""Extract text from an email."""
13+
14+
def load_file(self, path: str) -> None:
15+
print(f"Loading email from {path}")
16+
17+
def extract_text(self) -> str:
18+
return "Extracted email text"
19+
20+
21+
def read(reader, path: str) -> str:
22+
reader.load_file(path)
23+
return reader.extract_text()
24+
25+
26+
print(read(PdfReader(), "/reports/report.pdf"))
27+
print(read(EmailReader(), "/mail/message.eml"))
28+
29+
# class EmailReader:
30+
# """Extract text from an email."""
31+
#
32+
# def load_file(self, path: str) -> None:
33+
# print(f"Loading email from {path}")
34+
#
35+
# def extract_email_text(self) -> str:
36+
# return "Extracted email text"

python-interface/readers_abc.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class FileReaderInterface(ABC):
5+
"""Interface for file readers."""
6+
7+
@abstractmethod
8+
def load_file(self, path: str) -> None:
9+
"""Load a file for text extraction."""
10+
11+
@abstractmethod
12+
def extract_text(self) -> str:
13+
"""Return text extracted from the loaded file."""
14+
15+
16+
class PdfReader(FileReaderInterface):
17+
"""Extract text from a PDF."""
18+
19+
def load_file(self, path: str) -> None:
20+
"""Load a PDF file for text extraction."""
21+
print(f"Loading PDF from {path}")
22+
23+
def extract_text(self) -> str:
24+
"""Return text extracted from the loaded PDF."""
25+
return "Extracted PDF text"
26+
27+
28+
class EmailReader(FileReaderInterface):
29+
"""Extract text from an Email."""
30+
31+
def load_file(self, path: str) -> None:
32+
"""Load an EML file for text extraction."""
33+
print(f"Loading email from {path}")
34+
35+
def extract_email_text(self) -> str:
36+
"""Return text extracted from the loaded email."""
37+
return "Extracted email text"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import Protocol, runtime_checkable
2+
3+
4+
@runtime_checkable
5+
class FileReaderProtocol(Protocol):
6+
"""Protocol for file readers."""
7+
8+
def load_file(self, path: str) -> None:
9+
"""Load a file for text extraction."""
10+
...
11+
12+
def extract_text(self) -> str:
13+
"""Return text extracted from the loaded file."""
14+
...
15+
16+
17+
class PdfReader:
18+
"""Extract text from a PDF."""
19+
20+
def load_file(self, path: str) -> None:
21+
"""Load a PDF file for text extraction."""
22+
print(f"Loading PDF from {path}")
23+
24+
def extract_text(self) -> str:
25+
"""Return text extracted from the loaded PDF."""
26+
return "Extracted PDF text"
27+
28+
29+
class EmailReader:
30+
"""Extract text from an Email."""
31+
32+
def load_file(self, path: str) -> None:
33+
"""Load an EML file for text extraction."""
34+
print(f"Loading email from {path}")
35+
36+
def extract_text(self) -> str:
37+
"""Return text extracted from the loaded email."""
38+
return "Extracted email text"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from virtual_subclass import Double
2+
3+
print(issubclass(float, Double))
4+
print(isinstance(1.2345, Double))
5+
6+
7+
@Double.register
8+
class Double64:
9+
"""A 64-bit double-precision floating-point number."""
10+
11+
12+
print(issubclass(Double64, Double))
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from abc import ABC
2+
3+
4+
class Double(ABC):
5+
"""Double precision floating point number."""
6+
7+
8+
Double.register(float)

0 commit comments

Comments
 (0)