Czym są metody typu ”setters and getters” i jak je zaimplementowaćPythonie?

Odpowiedź

Metody tego typu są bardzo dobrze znane programistom języka Java. Służą one do pobierania i ustawiania pólobiektach. 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())

Uruchom w edytorze

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 metodsetters 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 getset. 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 getset 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)

Uruchom w edytorze

Output:

set marka
get marka
porsche
taycan

Jak widać posłużyliśmy się dekoratorami.

Oto jak zatem powinna wyglądać nasza praca:

  1. 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.
  2. Jeśli najdzie taka chwila, że ”getery” i ”setery” dla konkretnego pola są nam potrzebne używamy wbudowanych @property oraz @nazwa_pola.setter.
  3. 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ówkonstruktorze. 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 konstruktormetodysetters 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)

Uruchom w edytorze

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:

  1. Pisz klasy jak zwykle
  2. Jeśli kiedyś zajdzie potrzeba posiadania ”gettera” i ”settera” użyj poznanych w tym wpisie dekoratorów by zapewnić taką funkcjonalność.

Miłego programowania 🙂