Fedezd fel a Python absztrakt alaposztályainak (ABC) erejét. Tanuld meg a protokollalapú strukturális típusok és a formális interfészek közötti kritikus különbséget.
Python absztrakt alaposztályok: Protokollimplementáció vs. interfésztervezés elsajátítása
A szoftverfejlesztés világában a robusztus, karbantartható és skálázható alkalmazások építése a végső cél. Ahogy a projektek néhány szkriptből komplex rendszerekké nőnek, amelyeket nemzetközi csapatok kezelnek, az egyértelmű struktúra és a kiszámítható szerződések iránti igény kiemelkedővé válik. Hogyan biztosíthatjuk, hogy a különböző komponensek, amelyeket esetleg különböző fejlesztők írtak különböző időzónákban, zökkenőmentesen és megbízhatóan tudjanak együttműködni? A válasz az absztrakció elvében rejlik.
A Python, dinamikus természetével, híres absztrakciós filozófiával rendelkezik: "kacsa típusú". Ha egy objektum úgy jár, mint egy kacsa és úgy kuruttyol, mint egy kacsa, akkor kacsaként kezeljük. Ez a rugalmasság a Python egyik legnagyobb erőssége, amely gyors fejlesztést és tiszta, olvasható kódot ösztönöz. Azonban nagyméretű alkalmazásokban az implicit megállapodásokra való kizárólagos támaszkodás finom hibákhoz és karbantartási fejfájáshoz vezethet. Mi történik, amikor egy "kacsa" váratlanul nem tud repülni? Itt lépnek színre a Python absztrakt alaposztályai (ABC-k), amelyek egy hatékony mechanizmust biztosítanak a formális szerződések létrehozásához anélkül, hogy feladnánk a Python dinamikus szellemét.
Itt azonban egy kritikus és gyakran félreértett megkülönböztetés rejlik. A Pythonban az ABC-k nem egy méret mindenkire illik típusú eszközök. Két különálló, erőteljes szoftvertervezési filozófiát szolgálnak: explicit, formális interfészek létrehozása, amelyek öröklést igényelnek, és rugalmas protokollok definiálása, amelyek a képességeket ellenőrzik. E két megközelítés közötti különbség megértése – interfésztervezés kontra protokollimplementáció – a kulcs a Python objektumorientált tervezésének teljes potenciáljának felszabadításához, és olyan kód írásához, amely egyszerre rugalmas és biztonságos. Ez az útmutató mindkét filozófiát feltárja, gyakorlati példákat és világos útmutatást nyújtva arról, hogy mikor használjuk az egyes megközelítéseket a globális szoftverprojektekben.
Megjegyzés a formázáshoz: A speciális formázási korlátozások betartása érdekében a cikkben található kódpéldák standard szöveges címkékben, félkövér és dőlt stílusokkal vannak bemutatva. A legjobb olvashatóság érdekében azt javasoljuk, hogy másolja be őket a szerkesztőbe.
Az Alapok: Mi is pontosan az absztrakt alaposztály?
Mielőtt belemerülnénk a két tervezési filozófiába, alapozzuk meg a szilárd alapot. Mi az absztrakt alaposztály? Lényegében egy sablon más osztályok számára. Meghatároz egy sor metódust és tulajdonságot, amelyeket minden megfelelő leszármazottnak implementálnia kell. Ez egy módja annak kimondására: "Minden osztály, amely azt állítja, hogy ennek a családnak a része, rendelkeznie kell ezekkel a specifikus képességekkel."
A Python beépített `abc` modulja biztosítja az eszközöket az ABC-k létrehozásához. A két fő komponens a következő:
- `ABC`: Egy segédosztály, amelyet metaclassként használnak egy ABC létrehozásához. A modern Pythonban (3.4+) egyszerűen örökölhet az `abc.ABC`-től.
- `@abstractmethod`: Egy dekorátor, amelyet metódusok absztraktként való megjelölésére használnak. Az ABC minden leszármazottjának implementálnia kell ezeket a metódusokat.
Két alapvető szabály létezik, amely az ABC-ket irányítja:
- Nem hozhat létre példányt egy olyan ABC-ről, amelynek nincsenek implementálva az absztrakt metódusai. Ez egy sablon, nem egy kész termék.
- Minden konkrét leszármazottnak implementálnia kell az összes örökölt absztrakt metódust. Ha ezt elmulasztja, akkor maga is absztrakt osztály válik, és nem hozhat létre róla példányt.
Nézzük ezt működés közben egy klasszikus példával: egy médiatartalom-fájlok kezelésére szolgáló rendszer.
Példa: Egyszerű MediaFile ABC
Képzeljük el, hogy egy olyan alkalmazást építünk, amelynek különféle típusú médiatartalmat kell kezelnie. Tudjuk, hogy minden médiafájlnak, formátumától függetlenül, lejátszhatónak kell lennie, és rendelkeznie kell bizonyos metaadatokkal. Ezt a szerződést egy ABC-vel definiálhatjuk.
import abc
class MediaFile(abc.ABC):
def __init__(self, filepath: str):
self.filepath = filepath
print(f"Base init for {self.filepath}")
@abc.abstractmethod
def play(self) -> None:
"""Play the media file."""
raise NotImplementedError
@abc.abstractmethod
def get_metadata(self) -> dict:
"""Return a dictionary of media metadata."""
raise NotImplementedError
Ha megpróbálnánk közvetlenül létrehozni egy `MediaFile` példányt, a Python megállítana minket:
# Ez egy TypeError-t fog kiváltani
# media = MediaFile("path/to/somefile.txt")
# TypeError: Can't instantiate abstract class MediaFile with abstract methods get_metadata, play
Ahhoz, hogy ezt a sablont használhassuk, létre kell hoznunk konkrét leszármazott osztályokat, amelyek megvalósítják a `play()` és a `get_metadata()` metódusokat.
class AudioFile(MediaFile):
def play(self) -> None:
print(f"Playing audio from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "mp3", "duration_seconds": 180}
class VideoFile(MediaFile):
def play(self) -> None:
print(f"Playing video from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "h264", "resolution": "1920x1080"}
Most már létrehozhatunk `AudioFile` és `VideoFile` példányokat, mert teljesítik a `MediaFile` által meghatározott szerződést. Ez az ABC-k alapmechanizmusa. De az igazi erő az, ahogyan ezt a mechanizmust használjuk.
Az Első Filozófia: ABC-k Formális Interfésztervezésként (Nominális Típusok)
Az ABC-k használatának első és legtradicionálisabb módja a formális interfésztervezés. Ez a megközelítés a nominális típusokon alapul, amely fogalom ismerős a Java, C++ vagy C# nyelvekből érkező fejlesztők számára. A nominális rendszerben egy típus kompatibilitását a neve és a kifejezett deklarációja határozza meg. Kontextusunkban egy osztályt `MediaFile`-nak tekintünk, csak akkor, ha kifejezetten örököl a `MediaFile` ABC-től.
Gondoljon rá úgy, mint egy professzionális tanúsítványra. Ahhoz, hogy tanúsított projektmenedzser lehessen, nem elég csak úgy viselkednie; tanulnia kell, át kell adnia egy specifikus vizsgát, és hivatalos tanúsítványt kell kapnia, amely kifejezetten megállapítja a képesítését. A tanúsítvány neve és származása számít.
Ebben a modellben az ABC nem-tárgyalható szerződésként működik. Az örökléssel az osztály formális ígéretet tesz a rendszer többi részének, hogy biztosítja a szükséges funkcionalitást.
Példa: Adat exportáló keretrendszer
Képzeljük el, hogy egy olyan keretrendszert építünk, amely lehetővé teszi a felhasználók számára az adatok különféle formátumokba exportálását. Biztosítani akarjuk, hogy minden exportáló beépülő modul szigorú szerkezetnek feleljen meg. Definiálhatunk egy `DataExporter` interfészt.
import abc
from datetime import datetime
class DataExporter(abc.ABC):
"""A formal interface for data exporting classes."""
@abc.abstractmethod
def export(self, data: list[dict]) -> str:
"""Exports data and returns a status message."""
pass
def get_timestamp(self) -> str:
"""A concrete helper method shared by all subclasses."""
return datetime.utcnow().isoformat()
class CSVExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.csv"
print(f"Exporting {len(data)} rows to {filename}")
# ... actual CSV writing logic ...
return f"Successfully exported to {filename}"
class JSONExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.json"
print(f"Exporting {len(data)} records to {filename}")
# ... actual JSON writing logic ...
return f"Successfully exported to {filename}"
Itt a `CSVExporter` és a `JSONExporter` kifejezetten és ellenőrizhetően `DataExporter`-ek. A mi alkalmazásunk alaplogikája biztonságosan támaszkodhat erre a szerződésre:
def run_export_process(exporter: DataExporter, data_to_export: list[dict]):
print("--- Starting export process ---")
if not isinstance(exporter, DataExporter):
raise TypeError("Exporter must be a valid DataExporter implementation.")
status = exporter.export(data_to_export)
print(f"Process finished with status: {status}")
# Usage
data = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
run_export_process(CSVExporter(), data)
run_export_process(JSONExporter(), data)
Figyelje meg, hogy az ABC egy konkrét metódust, a `get_timestamp()`-ot is biztosít, amely megosztott funkcionalitást kínál minden leszármazott számára. Ez egy gyakori és hatékony minta az interfészalapú tervezésben.
A Formális Interfész Megközelítés Előnyei és Hátrányai
Előnyök:
- Kétértelműség nélküli és Explicit: A szerződés kristálytiszta. Egy fejlesztő láthatja az öröklési sort `class CSVExporter(DataExporter):` és azonnal megértheti az osztály szerepét és képességeit.
- Eszközbarát: Az IDE-k, a linting eszközök és a statikus elemző eszközök könnyen ellenőrizhetik a szerződést, kiváló automatikus kiegészítést és hibaellenőrzést biztosítva.
- Megosztott Funkcionalitás: Az ABC-k konkrét metódusokat biztosíthatnak, igazi alaposztályként működve és csökkentve a kódismétlést.
- Ismerősség: Ez a minta azonnal felismerhető a többi objektumorientált nyelv többségéből származó fejlesztők számára.
Hátrányok:
- Szoros összekapcsolódás: A konkrét osztály most közvetlenül az ABC-hez kötődik. Ha az ABC-t át kell helyezni vagy módosítani kell, minden leszármazott érintett.
- Merevség: Szigorú hierarchikus kapcsolatot kényszerít. Mi van, ha egy osztály logikailag exportálóként működhetne, de már egy másik, nélkülözhetetlen alaposztályból örököl? A Python többes öröklése megoldhatja ezt, de saját bonyolultságokat is bevezethet (mint a Gyémánt Probléma).
- Behatoló: Nem használható harmadik féltől származó kód adaptálására. Ha egy könyvtárat használ, amely rendelkezik egy `export()` metódussal, nem teheti azt `DataExporter`-ré anélkül, hogy nem örökölne tőle (ami nem lehetséges vagy nem kívánatos lehet).
A Második Filozófia: ABC-k Protokollimplementációként (Strukturális Típusok)
A második, inkább "Pythonos" filozófia a kacsa típusú megközelítéshez igazodik. Ez a megközelítés strukturális típusokat használ, ahol a kompatibilitást nem a név vagy a származás, hanem a struktúra és a viselkedés határozza meg. Ha egy objektumnak megvannak a szükséges metódusai és attribútumai a feladat elvégzéséhez, akkor a feladathoz megfelelő típusnak tekintik, függetlenül a deklarált osztályhierarchiájától.
Gondoljon az úszás képességére. Ahhoz, hogy úszónak tekintsék, nincs szüksége tanúsítványra vagy arra, hogy egy "úszó" családfa része legyen. Ha vízben tud magát hajtani anélkül, hogy elsüllyedne, akkor szerkezetileg úszó. Egy ember, egy kutya és egy kacsa is lehet úszó.
Az ABC-k használhatók ennek a koncepciónak a formalizálására. Az öröklés kényszerítése helyett definiálhatunk egy ABC-t, amely virtuális leszármazottakként ismeri fel más osztályokat, ha megvalósítják a szükséges protokollt. Ezt egy speciális mágikus metódussal érhetjük el: `__subclasshook__`.
Amikor meghívja az `isinstance(obj, MyABC)` vagy az `issubclass(SomeClass, MyABC)` parancsot, a Python először explicit öröklést keres. Ha ez nem sikerül, akkor ellenőrzi, hogy van-e `MyABC`-nek `__subclasshook__` metódusa. Ha van, akkor a Python meghívja, megkérdezve: "Hé, ezt az osztályt a tiéd leszármazottjának tekinted?" Ez lehetővé teszi az ABC számára, hogy struktúrán alapuló tagsági kritériumokat határozzon meg.
Példa: Egy `Serializable` protokoll
Definiáljunk egy protokollt a szótárba serializálható objektumokhoz. Nem akarjuk, hogy minden serializálható objektum a rendszerünkben örököljön egy közös alaposztályt. Ezek lehetnek adatbázis modellek, adatátviteli objektumok vagy egyszerű tárolók.
import abc
class Serializable(abc.ABC):
@abc.abstractmethod
def to_dict(self) -> dict:
pass
@classmethod
def __subclasshook__(cls, C):
if cls is Serializable:
# Check if 'to_dict' is in the method resolution order of C
if any("to_dict" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Most hozzunk létre néhány osztályt. Lényeges, hogy egyik sem fog örökölni a `Serializable`-től.
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def to_dict(self) -> dict:
return {"name": self.name, "email": self.email}
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
# This class does NOT conform to the protocol
class Configuration:
def __init__(self, setting: str):
self.setting = setting
Ellenőrizzük őket a protokollunkkal szemben:
print(f"Is User serializable? {isinstance(User('Test', 't@t.com'), Serializable)}")
print(f"Is Product serializable? {isinstance(Product('T-1000', 99.99), Serializable)}")
print(f"Is Configuration serializable? {isinstance(Configuration('ON'), Serializable)}")
# Output:
# Is User serializable? True
# Is Product serializable? False <- Wait, why? Let's fix this.
# Is Configuration serializable? False
Ah, egy érdekes hiba! A `Product` osztályunknak nincs `to_dict` metódusa. Adjunk hozzá egyet.
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
def to_dict(self) -> dict: # Adding the method
return {"sku": self.sku, "price": self.price}
print(f"Is Product now serializable? {isinstance(Product('T-1000', 99.99), Serializable)}")
# Output:
# Is Product now serializable? True
Annak ellenére, hogy a `User` és a `Product` nem rendelkezik közös szülőosztállyal (az `object` kivételével), rendszerünk mindkettőt `Serializable`-ként kezelheti, mert teljesítik a protokollt. Ez hihetetlenül erőteljes a szétkapcsolás szempontjából.
A Protokoll Megközelítés Előnyei és Hátrányai
Előnyök:
- Maximális Rugalmasság: Rendkívül laza összekapcsolódást ösztönöz. A komponensek csak a viselkedéssel törődnek, nem az implementáció származásával.
- Alkalmazkodóképesség: Tökéletes létező kód adaptálására, különösen harmadik féltől származó könyvtárakból, hogy illeszkedjenek a rendszerünk interfészeihez anélkül, hogy az eredeti kódot módosítanánk.
- Összetétel ösztönzése: Olyan tervezési stílust ösztönöz, ahol az objektumokat független képességekből építik fel, ahelyett, hogy mély, merev öröklési fákon keresztül.
Hátrányok:
- Implicit Szerződés: Az osztály és az általa megvalósított protokoll közötti kapcsolat nem nyilvánvaló az osztálydefinícióból. Egy fejlesztőnek esetleg át kell kutatnia a kódbázist, hogy megértse, miért kezelik a `User` objektumot `Serializable`-ként.
- Futásidejű Többletterhelés: Az `isinstance` ellenőrzés lassabb lehet, mivel meg kell hívnia a `__subclasshook__`-t, és ellenőrzéseket kell végeznie az osztály metódusain.
- Komplexitási Potenciál: A `__subclasshook__` logikája meglehetősen bonyolult lehet, ha a protokoll több metódust, argumentumot vagy visszatérési típust foglal magában.
A Modern Szintézis: `typing.Protocol` és Statikus Elemzés
Ahogy a Python nagyméretű rendszerekben való használata nőtt, úgy nőtt az igény a jobb statikus elemzés iránt is. A `__subclasshook__` megközelítés erőteljes, de tisztán futásidejű mechanizmus. Mi lenne, ha még a kód futtatása előtt megszerezhetnénk a strukturális típusok előnyeit?
Ez vezetett a `typing.Protocol` (PEP 544) bevezetéséhez. Szabványosított és elegáns módot biztosít protokollok definiálására, amelyek elsősorban statikus típusellenőrzőkhöz, mint a Mypy, Pyright vagy a PyCharm felügyelője készültek.
Egy `Protocol` osztály hasonlóan működik a mi `__subclasshook__` példánkhoz, de a boilerplate nélkül. Egyszerűen definiálja a metódusokat és azok szignatúráját. Minden olyan osztály, amelynek megfelelő metódusai és szignatúrái vannak, statikus típusellenőrző által strukturálisan kompatibilisnek tekinthető.
Példa: Egy `Quacker` protokoll
Térjünk vissza a klasszikus kacsa típusú példához, de modern eszközökkel.
from typing import Protocol
class Quacker(Protocol):
def quack(self, volume: int) -> str:
"""Produces a quacking sound."""
... # Note: The body of a protocol method is not needed
class Duck:
def quack(self, volume: int) -> str:
return f"QUACK! (at volume {volume})"
class Dog:
def bark(self, volume: int) -> str:
return f"WOOF! (at volume {volume})"
def make_sound(animal: Quacker):
print(animal.quack(10))
make_sound(Duck()) # Static analysis passes
make_sound(Dog()) # Static analysis fails!
Ha ezt a kódot futtatja egy típusellenőrzőn, mint a Mypy, akkor a `make_sound(Dog())` sort hibával fogja jelölni: `Argument 1 to "make_sound" has incompatible type "Dog"; expected "Quacker"`. A típusellenőrző megérti, hogy a `Dog` nem teljesíti a `Quacker` protokollt, mert hiányzik belőle a `quack` metódus. Ez még a kód futtatása előtt elkapja a hibát.
Futásidejű Protokollok a `@runtime_checkable` segítségével
Alapértelmezetten a `typing.Protocol` csak statikus elemzésre szolgál. Ha megpróbálja futásidejű `isinstance` ellenőrzésben használni, hibát fog kapni.
# isinstance(Duck(), Quacker) # -> TypeError: Protocol 'Quacker' cannot be instantiated
Azonban a statikus elemzés és a futásidejű viselkedés közötti hidat a `@runtime_checkable` dekorátorral hozhatja létre. Ez lényegében azt mondja a Pythonnak, hogy automatikusan generálja a `__subclasshook__` logikát.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Quacker(Protocol):
def quack(self, volume: int) -> str: ...
class Duck:
def quack(self, volume: int) -> str: return "..."
print(f"Is Duck an instance of Quacker? {isinstance(Duck(), Quacker)}")
# Output:
# Is Duck an instance of Quacker? True
Ez a legjobb mindkét világból: tiszta, deklaratív protokoll definíciók statikus elemzéshez, és a lehetőség futásidejű érvényesítésre, amikor szükséges. Azonban érdemes megjegyezni, hogy a protokollokon végzett futásidejű ellenőrzések lassabbak, mint a standard `isinstance` hívások, ezért óvatosan kell használni őket.
Gyakorlati Döntéshozatal: Útmutató Globális Fejlesztőknek
Tehát melyik megközelítést válassza? A válasz teljesen a specifikus használati esettől függ. Íme egy praktikus útmutató a nemzetközi szoftverprojektekben előforduló gyakori forgatókönyvek alapján.
1. Forgatókönyv: Bővítmény Architektúra Kialakítása Egy Globális SaaS Termékhez
Egy olyan rendszert tervez (pl. e-kereskedelmi platform, CMS), amelyet első és harmadik féltől származó fejlesztők bővítenek világszerte. Ezeknek a beépülő moduloknak mélyen integrálódniuk kell az alapalkalmazásba.
- Ajánlás: Formális Interfész (Nominális `abc.ABC`).
- Indoklás: A tisztaság, a stabilitás és az explicititás kiemelkedő fontosságú. Egy nem-tárgyalható szerződésre van szüksége, amelyre a beépülő modul fejlesztőknek tudatosan opt-in kell, hogy örököljenek az Ön `BasePlugin` ABC-jétől. Ez teszi az API-t kétértelműség nélküli. Az alaposztályban alapvető segédmetódusokat is biztosíthat (pl. naplózáshoz, konfigurációhoz, nemzetköziesítéshez való hozzáféréshez), ami hatalmas előny a fejlesztői ökoszisztéma számára.
2. Forgatókönyv: Pénzügyi Adatok Feldolgozása Több, Nem Kapcsolódó API-ból
A fintech alkalmazásának különböző globális fizetési átjárókból kell tranzakciós adatokat fogyasztania: Stripe, PayPal, Adyen, és talán egy regionális szolgáltató, mint a Mercado Pago Latin-Amerikában. A SDK-ik által visszaadott objektumok teljesen az Ön irányítása alatt állnak.
- Ajánlás: Protokoll (`typing.Protocol`).
- Indoklás: Nem módosíthatja e harmadik féltől származó SDK-k forráskódját, hogy örököljenek a `Transaction` alaposztályunktól. Tudjuk azonban, hogy mindegyik tranzakció objektumuk rendelkezik olyan metódusokkal, mint a `get_id()`, `get_amount()` és `get_currency()`, még akkor is, ha kissé eltérően nevezik őket. Az Adapter minta használható a `TransactionProtocol`-lal együtt egy egységes nézet létrehozásához. Egy protokoll lehetővé teszi az általunk szükséges adatok formájának definiálását, lehetővé téve számunkra, hogy olyan feldolgozási logikát írjunk, amely bármely adatforrással működik, amíg az adaptálható a protokollhoz.
3. Forgatókönyv: Egy Nagy, Monolitikus Öröklött Alkalmazás Átalakítása
Feladatunk egy öröklött monolit modern mikroszolgáltatásokká bontása. A meglévő kód rendetlen függőségi háló, és egyértelmű határokat kell bevezetnünk anélkül, hogy mindent egyszerre írnánk át.
- Ajánlás: Keverék, de erősen támaszkodjon a protokollokra.
- Indoklás: A protokollok kivételes eszközök az fokozatos átalakításhoz. Kezdheti az új szolgáltatások közötti ideális interfészek meghatározásával a `typing.Protocol` használatával. Ezután adaptereket írhat az öröklött monolit részeihez, hogy megfeleljenek ezeknek a protokolloknak anélkül, hogy azonnal megváltoztatná a core öröklött kódot. Ez lehetővé teszi a komponensek fokozatos szétkapcsolását. Miután egy komponens teljesen szétkapcsolódott, és csak a protokollon keresztül kommunikál, készen áll a saját szolgáltatásba történő kivonásra. Formális ABC-k használhatók később az új, tiszta szolgáltatásokon belüli core modellek meghatározására.
Következtetés: Az Absztrakció Beépítése a Kódba
A Python absztrakt alaposztályai a nyelv pragmatikus tervezésének bizonyítékai. Fejlett eszköztárat kínálnak az absztrakcióhoz, amely tiszteletben tartja mind a hagyományos objektumorientált programozás strukturális fegyelmét, mind a kacsa típusú dinamikus rugalmasságát.
Az implicit megállapodástól a formális szerződésig tartó út egy érett kódbázis jele. Az ABC-k két filozófiájának megértésével megalapozott építészeti döntéseket hozhat, amelyek tisztább, jobban karbantartható és rendkívül skálázható alkalmazásokhoz vezetnek.
Összefoglalva a kulcsfontosságú elveket:
- Formális Interfész Tervezés (Nominális Típusok): Használja az `abc.ABC`-t közvetlen örökléssel, amikor explicit, kétértelműség nélküli és felfedezhető szerződésre van szüksége. Ideális keretrendszerekhez, beépülő modul rendszerekhez és olyan helyzetekhez, ahol Ön irányítja az osztályhierarchiát. Ez az, hogy milyen osztály a deklaráció szerint.
- Protokoll Implementáció (Strukturális Típusok): Használja a `typing.Protocol`-t, amikor rugalmasságra, szétkapcsolódásra és a létező kód adaptálásának képességére van szüksége. Tökéletes külső könyvtárakkal való munkához, öröklött rendszerek átalakításához és viselkedési polimorfizmusra való tervezéshez. Ez arról szól, hogy mit tud egy osztály csinálni a struktúrája alapján.
Az interfész és a protokoll közötti választás nem csupán műszaki részlet; ez egy alapvető tervezési döntés, amely meghatározza, hogyan fog fejlődni a szoftvere. Mindkettő elsajátításával felvértezheti magát ahhoz, hogy olyan Python kódot írjon, amely nemcsak erőteljes és hatékony, hanem elegáns és rugalmas is a változásokkal szemben.