Python abstract methods: polimorfizmas

Kas tie abstraktūs metodai ir kodėl jie reikalingi

Kai pradedi gilintis į objektinį programavimą Python’e, anksčiau ar vėliau susiduri su abstrakčiais metodais. Pirmą kartą pamačius tokį kodą, gali kilti klausimas – kam čia dar viena komplikacija? Juk Python ir taip yra lankstus, dinamiškas, leidžia daryti beveik bet ką. Tačiau abstraktūs metodai egzistuoja ne tam, kad apsunkintų gyvenimą, o tam, kad padėtų kurti tvarkingesnę ir aiškesnę programų architektūrą.

Abstraktūs metodai – tai savotiškas kontraktas tarp bazinės klasės ir jos palikuonių. Kai sukuri abstraktų metodą bazinėje klasėje, iš esmės sakai: „Klausyk, bet kuri klasė, kuri paveldės iš manęs, PRIVALO implementuoti šį metodą”. Tai kaip architektūrinis brėžinys – nurodai, kas turi būti, bet nesakai, kaip tiksliai tai turi veikti.

Kaip praktiškai sukurti abstrakčią klasę

Python’e abstrakčioms klasėms kurti naudojamas abc modulis (Abstract Base Classes). Štai paprastas pavyzdys:

„`python
from abc import ABC, abstractmethod

class Transportas(ABC):
@abstractmethod
def vaziuoti(self):
pass

@abstractmethod
def sustoti(self):
pass
„`

Matai tą @abstractmethod dekoratorių? Tai ir yra raktas. Dabar, jei bandysi sukurti Transportas objektą tiesiogiai, Python meta klaidą. Ir teisingai daro – abstrakti klasė yra kaip šablonas, ne galutinis produktas.

Dabar sukurkime konkrečią klasę:

„`python
class Automobilis(Transportas):
def vaziuoti(self):
print(„Automobilis važiuoja keturiais ratais”)

def sustoti(self):
print(„Spaudžiame stabdžius”)

class Dviratis(Transportas):
def vaziuoti(self):
print(„Sukame pedalus”)

def sustoti(self):
print(„Naudojame rankines stabdžius”)
„`

Štai dabar viskas veikia. Abi klasės implementavo privalomas funkcijas. Jei būtum pamiršęs implementuoti bent vieną abstraktų metodą, Python’as iš karto praneštų apie problemą bandant sukurti objektą.

Polimorfizmas veikime – kodėl tai galingas dalykas

Dabar prie smagiausios dalies. Polimorfizmas leidžia elgtis su skirtingais objektais vienodai, kol jie turi bendrą interfeisą. Skamba abstrakčiai? Pažiūrėkim į kodą:

„`python
def testuoti_transporta(transportas):
transportas.vaziuoti()
transportas.sustoti()

auto = Automobilis()
dviratis = Dviratis()

testuoti_transporta(auto)
testuoti_transporta(dviratis)
„`

Matai, kas vyksta? Funkcija testuoti_transporta nė nežino, ar ji gauna automobilį, ar dviratį. Jai nerūpi. Ji žino tik tiek, kad objektas turi metodus vaziuoti() ir sustoti(). Tai ir yra polimorfizmas – viena sąsaja, daug implementacijų.

Realiame projekte tai reiškia, kad gali rašyti bendrą logiką, kuri veiks su bet kokiu transporto tipu. Vėliau pridėsi naują klasę Lektuvas ar Laivas, ir visa esama logika veiks be jokių pakeitimų. Tai sutaupo laiko ir sumažina klaidų tikimybę.

Abstraktūs metodai su parametrais ir grąžinimo reikšmėmis

Gyvenime metodai retai būna tokie paprasti kaip vaziuoti() be parametrų. Dažniausiai reikia perduoti duomenis ir gauti rezultatus. Gera žinia – abstraktūs metodai puikiai dirba su parametrais:

„`python
from abc import ABC, abstractmethod

class DuomenuBaze(ABC):
@abstractmethod
def prisijungti(self, host, port, vartotojas):
pass

@abstractmethod
def vykdyti_uzklausa(self, sql):
pass

@abstractmethod
def atsijungti(self):
pass

class PostgreSQL(DuomenuBaze):
def prisijungti(self, host, port, vartotojas):
print(f”Jungiamasi prie PostgreSQL: {host}:{port}”)
# Čia būtų tikroji prisijungimo logika

def vykdyti_uzklausa(self, sql):
print(f”Vykdoma PostgreSQL užklausa: {sql}”)
return [] # Grąžintų rezultatus

def atsijungti(self):
print(„Atsijungiama nuo PostgreSQL”)

class MongoDB(DuomenuBaze):
def prisijungti(self, host, port, vartotojas):
print(f”Jungiamasi prie MongoDB: {host}:{port}”)

def vykdyti_uzklausa(self, sql):
print(f”Verčiama į MongoDB užklausą ir vykdoma”)
return []

def atsijungti(self):
print(„Atsijungiama nuo MongoDB”)
„`

Dabar turi bendrą sąsają darbui su bet kokia duomenų baze. Programos kode gali rašyti:

„`python
def apdoroti_duomenis(db: DuomenuBaze):
db.prisijungti(„localhost”, 5432, „admin”)
rezultatai = db.vykdyti_uzklausa(„SELECT * FROM users”)
db.atsijungti()
return rezultatai
„`

Ši funkcija veiks su PostgreSQL, MongoDB ar bet kokia kita duomenų baze, kurią implementuosi ateityje.

Mišrios klasės: abstraktūs ir konkretūs metodai kartu

Štai kur tampa įdomu – abstrakti klasė gali turėti ir abstrakčius, ir visiškai normalius metodus. Tai leidžia sukurti bazinę funkcionalumą, kurį paveldės visos vaikinės klasės, tuo pačiu paliekant lankstumo tam, kas turi skirtis:

„`python
from abc import ABC, abstractmethod

class Failas(ABC):
def __init__(self, pavadinimas):
self.pavadinimas = pavadinimas
self.atidarytas = False

@abstractmethod
def skaityti(self):
pass

@abstractmethod
def rasyti(self, duomenys):
pass

def atidaryti(self):
„””Bendras metodas visiems failų tipams”””
if not self.atidarytas:
print(f”Atidaromas failas: {self.pavadinimas}”)
self.atidarytas = True
else:
print(„Failas jau atidarytas”)

def uzdaryti(self):
„””Bendras metodas visiems failų tipams”””
if self.atidarytas:
print(f”Uždaromas failas: {self.pavadinimas}”)
self.atidarytas = False

class TekstoFailas(Failas):
def skaityti(self):
return „Teksto turinys…”

def rasyti(self, duomenys):
print(f”Rašoma teksto formatu: {duomenys}”)

class JSONFailas(Failas):
def skaityti(self):
return {„raktas”: „reikšmė”}

def rasyti(self, duomenys):
print(f”Rašoma JSON formatu: {duomenys}”)
„`

Dabar atidaryti() ir uzdaryti() metodai veikia vienodai visoms failų rūšims, o skaityti() ir rasyti() kiekviena klasė implementuoja savaip. Tai sutaupo daug kodo dubliavimo.

Kada NEVERTA naudoti abstrakčių metodų

Python bendruomenėje kartais pasigirsta nuomonė, kad abstrakčios klasės – tai per daug „Java stiliaus” Python’ui. Ir iš dalies tai tiesa. Python filosofija yra „duck typing” – jei kažkas elgiasi kaip antis, tai tikriausiai ir yra antis. Nebūtinai reikia formaliai deklaruoti, kad tai antis.

Štai keletas situacijų, kai abstrakčios klasės gali būti overkill:

Mažuose projektuose – jei rašai 200 eilučių skriptą duomenų analizei, abstrakčios klasės tik apsunkins gyvenimą. Paprastas kodas čia geriau.

Kai dirbi vienas – jei niekas kitas neskaitys tavo kodo, ir projektas nėra didelis, formalios struktūros gali būti nereikalingos.

Prototipuojant – kai dar nežinai, kaip tiksliai atrodys galutinė architektūra, per anksti įvesti abstrakčias klases gali trukdyti eksperimentuoti.

Su trečiųjų šalių bibliotekomis – jei integruoji išorines bibliotekas, kurios neturi bendros bazinės klasės, versti jas į savo abstrakčią hierarchiją gali būti nepatogu.

Tačiau kai projektas auga, kai dirbi komandoje, kai žinai, kad bus daug panašių klasių su bendra logika – abstrakčios klasės tampa tikru išganymu.

Praktiniai patarimai darbui su abstrakčiais metodais

Naudok type hints – Python 3.5+ palaiko tipų anotacijas, kurios puikiai dera su abstrakčiomis klasėmis:

„`python
from abc import ABC, abstractmethod
from typing import List, Dict

class DuomenuSaugykla(ABC):
@abstractmethod
def issaugoti(self, duomenys: Dict[str, any]) -> bool:
pass

@abstractmethod
def gauti_visus(self) -> List[Dict[str, any]]:
pass
„`

Dabar IDE ir linteriai galės patikrinti, ar teisingai naudoji metodus.

Dokumentuok abstrakčius metodus – net jei metodas tuščias, parašyk docstring, paaiškinantį, ką jis turėtų daryti:

„`python
@abstractmethod
def apdoroti(self, duomenys):
„””
Apdoroja gautus duomenis pagal konkrečios klasės logiką.

Args:
duomenys: Neapdoroti duomenys bet kokiu formatu

Returns:
Apdoroti duomenys standartizuotu formatu

Raises:
ValueError: Jei duomenys netinkami apdorojimui
„””
pass
„`

Neperdesk su abstrakcija – ne kiekviena klasė turi būti abstrakti. Jei turi tik vieną implementaciją ir neplanuoji daugiau, paprasta klasė visiškai tinka.

Testuok su isinstance ir issubclass:

„`python
auto = Automobilis()
print(isinstance(auto, Transportas)) # True
print(issubclass(Automobilis, Transportas)) # True
„`

Tai naudinga validuojant įvestis funkcijose.

Kai viskas susideda į vieną paveikslą

Abstraktūs metodai ir polimorfizmas – tai ne tik teorinės sąvokos iš vadovėlių. Tai praktiniai įrankiai, kurie padeda rašyti lankstesnį, lengviau prižiūrimą kodą. Kai suprojektuoji sistemą su aiškiomis abstrakcijomis, nauji reikalavimai tampa ne košmaru, o tiesiog dar viena implementacija.

Pradėk naudoti abstrakčias klases, kai matai pasikartojančius šablonus savo kode. Kai keli objektai daro panašius dalykus, bet šiek tiek skirtingai – tai idealus signalas. Sukurk bendrą bazę, apibrėžk, kas būtina, ir leisk konkrečioms klasėms pasirūpinti detalėmis.

Ir nepamirsk – Python’e abstrakčios klasės yra įrankis, ne dogma. Jei projektas paprastas, nejausk spaudimo viską paversti abstrakcijomis. Bet kai projektas auga, kai ateina nauji programuotojai, kai reikia pridėti naujų funkcijų nepažeidžiant senų – būsi dėkingas sau už gerai apgalvotą architektūrą su abstrakčiais metodais. Tai investicija į ateitį, kuri atsipirks šimteriopai.

Daugiau

Gloo Edge API gateway