Czym są metody typu ”setters and getters” i jak je zaimplementować w Pythonie?
Odpowiedź
Metody tego typu są bardzo dobrze znane programistom języka Java. Służą one do pobierania i ustawiania pól w obiektach. W najprostszym przypadku do pól klasy odwołujemy się bezpośrednio przez operator kropki:
class Auto: def __init__(self, marka, model): self.marka = marka self.model = model porsche = Auto(marka='porsche', model='taycan') print(porsche.marka) print(porsche.model)
Output:
porsche taycan
Pewnie powiesz ”nic specjalnego”. Stworzymy obiekt. Ustawimy mu wartości pól. Na koniec używając normalnego operatora ”.” wyświetliliśmy zawartość pól. Zastanówmy się jak by to wyglądało używając „setters and getters„:
class Auto: def __init__(self): self.marka = None self.model = None def set_marka(self, marka): self.marka = marka def get_marka(self): return self.marka def set_model(self, model): self.model = model def get_model(self): return self.model porsche = Auto() porsche.set_marka('porsche') porsche.set_model('taycan') print(porsche.get_marka()) print(porsche.get_model())
Output:
porsche taycan
Wygląda to tragicznie. Widząc taki kod napisany w Pythonie na 100% masz do czynienia z byłym programistą Javy. Już wiemy, że udało nam się wydłużyć i skomplikować nasz program. Zastanówmy się co udało na się zyskać takim podejściem.
Używanie metod „setters and getters” ma na celu oddzielnie konkretnej implementacji naszych parametrów od ich obsługi przez obiekty zewnętrzne. Metody te są interfejsem naszego obiektu. Jeśli zapragniemy zmienić nazwy naszych pół żaden problem. Podmieniamy tylko implementację naszych ”getterów” i ”setterów”. Dla wszystkich współpracujących z nasza klasą komponentów pozostaje to niezauważone. Dalej korzystają z tych samych metod get i set. Cala magia dzieje się w środku. Zatem nasze klasy są bardziej podane na modyfikację. Klasycznym przykładem jest tu dodanie walidacji ustawianych parametrów.
Jak to powinno być zrobione
Jak już ustaliliśmy korzystanie z metod get i set daje nam sporo plusów dodatnich. Niestety obniża to czytelność naszego kodu. Twórcy języka rozwiązali ten problem. I śmiem stwierdzić, że lepiej nie można było tego zrobić. Do rzeczy jak to się robi w Pythonie:
class Auto: def __init__(self): self._marka = None self.model = None @property def marka(self): print('get marka') return self._marka @marka.setter def marka(self, marka): print('set marka') self._marka = marka porsche = Auto() porsche.marka = 'porsche' porsche.model = 'taycan' print(porsche.marka) print(porsche.model)
Output:
set marka get marka porsche taycan
Jak widać posłużyliśmy się dekoratorami.
Oto jak zatem powinna wyglądać nasza praca:
- Tworzymy nasz program jak by nigdy nic. Chwilo zapominamy o tym, że ”gettery” i ”settery” istnieją. Odwołujemy się do pól klasy normalnie używając operatora kropki.
- Jeśli najdzie taka chwila, że ”getery” i ”setery” dla konkretnego pola są nam potrzebne używamy wbudowanych @property oraz @nazwa_pola.setter.
- Dzięki tym dekoratorom w dalszym ciągu możesz odwoływać się do swojego pola poprzez operator kropki. Także z zewnątrz klasy wszytko pozostaje bez zmian. Świetnie. Nasza klasa ma zatem kompatybilność ze starym rozwiązaniem.
Zauważ, że stosowanie staromodnych (Javowych) ”setterów” i ”getterów” praktycznie wyklucza nam ustawianie parametrów w konstruktorze. Oczywiście Javovcy powiedzą że to dobrze, bo jeśli używamy ”setterów” i ”getterów” do ustawiania parametrów to przynajmniej widać jakie parametry są ustawiane. Nazwy metod mówią nam jaki parametr jest ustawiany. Z tym, że w Pythonie załatwiamy to nazywając parametry ustawiane w konstruktorze porsche = Auto(marka='porsche', model='taycan')
.
Nic nie stoi zatem na przeszkodzie by nasza klasa posiadała i konstruktor i metody ”setters and getters”:
class Auto: def __init__(self, marka, model): self.marka = marka self.model = model @property def marka(self): print('get marka') return self._marka @marka.setter def marka(self, marka): print('set marka') self._marka = marka @property def model(self): print('get model') return self._model @model.setter def model(self, model): print('set model') self._model = model porsche = Auto(marka='porsche', model='taycan') print(porsche.marka) print(porsche.model)
Output:
set marka set model get marka porsche get model taycan
Zauważ, że ustawiane w konstruktorze parametry również przechodzą przez nasze metody ustawiające i pobierające parametr. Podsumujmy to czego się nauczyliśmy:
- Pisz klasy jak zwykle
- Jeśli kiedyś zajdzie potrzeba posiadania ”gettera” i ”settera” użyj poznanych w tym wpisie dekoratorów by zapewnić taką funkcjonalność.
Miłego programowania 🙂