From c9a43e31c7cf9e31f2f9b804fda8c236ef4eb34b Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Tue, 21 Apr 2026 12:34:39 +0000 Subject: [PATCH] Sample code for: Implementing Interfaces in Python: ABCs and Protocols --- python-interface/README.md | 3 ++ python-interface/check_protocols.py | 11 +++++++ python-interface/check_virtual_classes.py | 5 +++ python-interface/create_readers.py | 7 +++++ python-interface/meta_classes.py | 37 ++++++++++++++++++++++ python-interface/readers.py | 36 +++++++++++++++++++++ python-interface/readers_abc.py | 37 ++++++++++++++++++++++ python-interface/readers_protocol.py | 38 +++++++++++++++++++++++ python-interface/registered_subclass.py | 12 +++++++ python-interface/virtual_subclass.py | 8 +++++ 10 files changed, 194 insertions(+) create mode 100644 python-interface/README.md create mode 100644 python-interface/check_protocols.py create mode 100644 python-interface/check_virtual_classes.py create mode 100644 python-interface/create_readers.py create mode 100644 python-interface/meta_classes.py create mode 100644 python-interface/readers.py create mode 100644 python-interface/readers_abc.py create mode 100644 python-interface/readers_protocol.py create mode 100644 python-interface/registered_subclass.py create mode 100644 python-interface/virtual_subclass.py diff --git a/python-interface/README.md b/python-interface/README.md new file mode 100644 index 0000000000..1190005ef7 --- /dev/null +++ b/python-interface/README.md @@ -0,0 +1,3 @@ +# Implementing Interfaces in Python: ABCs and Protocols + +This folder provides the code examples for the Real Python tutorial [Implementing Interfaces in Python: ABCs and Protocols](https://realpython.com/python-interface/) diff --git a/python-interface/check_protocols.py b/python-interface/check_protocols.py new file mode 100644 index 0000000000..dd6fa8c9a1 --- /dev/null +++ b/python-interface/check_protocols.py @@ -0,0 +1,11 @@ +from readers_protocols import FileReaderProtocol, PdfReader, EmailReader + + +def read(reader: FileReaderProtocol, path: str) -> str: + reader.load_file(path) + return reader.extract_text() + + +# Accepted by the type checker +read(PdfReader(), "/reports/report.pdf") +read(EmailReader(), "/mail/message.eml") diff --git a/python-interface/check_virtual_classes.py b/python-interface/check_virtual_classes.py new file mode 100644 index 0000000000..6aa427bc8e --- /dev/null +++ b/python-interface/check_virtual_classes.py @@ -0,0 +1,5 @@ +from meta_classes import EmailReader, PdfReader, VirtualFileReader + +print(isinstance(PdfReader(), VirtualFileReader)) +print(issubclass(PdfReader, VirtualFileReader)) +print(issubclass(EmailReader, VirtualFileReader)) diff --git a/python-interface/create_readers.py b/python-interface/create_readers.py new file mode 100644 index 0000000000..a023b9be8a --- /dev/null +++ b/python-interface/create_readers.py @@ -0,0 +1,7 @@ +from readers_abc import EmailReader, FileReaderInterface, PdfReader + +pdf_reader = PdfReader() +email_reader = EmailReader() + +print(isinstance(PdfReader(), FileReaderInterface)) +print(issubclass(PdfReader, FileReaderInterface)) diff --git a/python-interface/meta_classes.py b/python-interface/meta_classes.py new file mode 100644 index 0000000000..dcf22c0e7e --- /dev/null +++ b/python-interface/meta_classes.py @@ -0,0 +1,37 @@ +class ReaderMeta(type): + """A Reader metaclass used for reader class creation.""" + + def __instancecheck__(cls, instance): + return cls.__subclasscheck__(type(instance)) + + def __subclasscheck__(cls, subclass): + return ( + hasattr(subclass, "load_file") + and callable(subclass.load_file) + and hasattr(subclass, "extract_text") + and callable(subclass.extract_text) + ) + + +class VirtualFileReader(metaclass=ReaderMeta): + """Virtual class.""" + + +class PdfReader: + """Extract text from a PDF.""" + + def load_file(self, path: str) -> None: + print(f"Loading PDF from {path}") + + def extract_text(self) -> str: + return "Extracted PDF text" + + +class EmailReader: + """Extract text from an Email.""" + + def load_file(self, path: str) -> None: + print(f"Loading Email from {path}") + + def extract_email_text(self) -> str: + return "Extracted Email text" diff --git a/python-interface/readers.py b/python-interface/readers.py new file mode 100644 index 0000000000..751387c1b7 --- /dev/null +++ b/python-interface/readers.py @@ -0,0 +1,36 @@ +class PdfReader: + """Extract text from a PDF.""" + + def load_file(self, path: str) -> None: + print(f"Loading PDF from {path}") + + def extract_text(self) -> str: + return "Extracted PDF text" + + +class EmailReader: + """Extract text from an Email.""" + + def load_file(self, path: str) -> None: + print(f"Loading Email from {path}") + + def extract_text(self) -> str: + return "Extracted Email text" + + +def read(reader, path: str) -> str: + reader.load_file(path) + return reader.extract_text() + + +print(read(PdfReader(), "/reports/report.pdf")) +print(read(EmailReader(), "/mail/message.eml")) + +# class EmailReader: +# """Extract text from an Email.""" + +# def load_file(self, path: str) -> None: +# print(f"Loading Email from {path}") + +# def extract_email_text(self) -> str: +# return "Extracted Email text" diff --git a/python-interface/readers_abc.py b/python-interface/readers_abc.py new file mode 100644 index 0000000000..4c13bc959c --- /dev/null +++ b/python-interface/readers_abc.py @@ -0,0 +1,37 @@ +from abc import ABC, abstractmethod + + +class FileReaderInterface(ABC): + """Interface for file readers.""" + + @abstractmethod + def load_file(self, path: str) -> None: + """Load a file for text extraction.""" + + @abstractmethod + def extract_text(self) -> str: + """Return text extracted from the loaded file.""" + + +class PdfReader(FileReaderInterface): + """Extract text from a PDF.""" + + def load_file(self, path: str) -> None: + """Load a PDF file for text extraction.""" + print(f"Loading PDF from {path}") + + def extract_text(self) -> str: + """Return text extracted from the loaded PDF.""" + return "Extracted PDF text" + + +class EmailReader(FileReaderInterface): + """Extract text from an Email.""" + + def load_file(self, path: str) -> None: + """Load an EML file for text extraction.""" + print(f"Loading Email from {path}") + + def extract_email_text(self) -> str: + """Return text extracted from the loaded Email.""" + return "Extracted Email text" diff --git a/python-interface/readers_protocol.py b/python-interface/readers_protocol.py new file mode 100644 index 0000000000..d564fcd164 --- /dev/null +++ b/python-interface/readers_protocol.py @@ -0,0 +1,38 @@ +from typing import Protocol + + +# @runtime_checkable # Uncomment for runtime checks of protocol adherence +class FileReaderProtocol(Protocol): + """Protocol for file readers.""" + + def load_file(self, path: str) -> None: + """Load a file for text extraction.""" + ... + + def extract_text(self) -> str: + """Return text extracted from the loaded file.""" + ... + + +class PdfReader: + """Extract text from a PDF.""" + + def load_file(self, path: str) -> None: + """Load a PDF file for text extraction.""" + print("Loading your PDF...") + + def extract_text(self) -> str: + """Return text extracted from the loaded PDF.""" + return "Your PDF content" + + +class EmailReader: + """Extract text from an Email.""" + + def load_file(self, path: str) -> None: + """Load an EML file for text extraction.""" + print("Loading your Email...") + + def extract_text(self) -> str: + """Return text extracted from the loaded Email.""" + return "Your Email content" diff --git a/python-interface/registered_subclass.py b/python-interface/registered_subclass.py new file mode 100644 index 0000000000..6aa44811ef --- /dev/null +++ b/python-interface/registered_subclass.py @@ -0,0 +1,12 @@ +from virtual_subclass import Double + +print(issubclass(float, Double)) +print(isinstance(1.2345, Double)) + + +@Double.register +class Double64: + """A 64-bit double-precision floating-point number.""" + + +print(issubclass(Double64, Double)) diff --git a/python-interface/virtual_subclass.py b/python-interface/virtual_subclass.py new file mode 100644 index 0000000000..7f4e55f38b --- /dev/null +++ b/python-interface/virtual_subclass.py @@ -0,0 +1,8 @@ +from abc import ABC + + +class Double(ABC): + """Double precision floating point number.""" + + +Double.register(float)