Gettry i settery nazywane też akcesorami/mutatorami, wykorzystywane są odpowiednio do pobierania i ustawiania wartości atrybutu obiektu. Zapewniają one enkapsulacje danych. W Pythonie istnieje kilka przykładów, które na pierwszy rzut oka nie są trywialne. Przyjrzyjmy się im dokładniej.
Na początku zaprojektujmy sobie prostą klasę:
class CodeCouple(object): def __init__(self, name): self.__name = name def get_name(self): return self.__name def set_name(self, name): self.__name = name
Utworzyliśmy klasę CodeCouple, która ma jedno prywatne pole name. Dodaliśmy gettery i settery jako zwykłe metody. Przyjrzyjmy się ich wywołaniu:
agnieszka = CodeCouple('Agnieszka') krzysztof = CodeCouple('Empty') print(agnieszka.get_name()) krzysztof.set_name('Krzysztof') print(krzysztof.get_name()) code_couple = CodeCouple('Fill') code_couple.set_name(krzysztof.get_name() + ' ' + agnieszka.get_name()) print(code_couple.get_name())
Agnieszka Krzysztof Krzysztof Agnieszka
Jak widzicie wywołanie “code_couple.set_name(krzysztof.get_name() + ‘ ‘ + agnieszka.get_name())” nie jest zbyt ładne, nie jest w stylu Pythonic. Ponieważ nie zmieniamy logiki pobierania i ustawiania pola, nie trzeba tworzyć getterów i setterów, a wystarczy jedynie zadeklarować pole name jako publiczne:
class CodeCouple(object): def __init__(self, name): self.name = name
agnieszka = CodeCouple('Agnieszka') krzysztof = CodeCouple('Empty') print(agnieszka.name) krzysztof.name = 'Krzysztof' print(krzysztof.name) code_couple = CodeCouple('Fill') code_couple.name = krzysztof.name + ' ' + agnieszka.name print(code_couple.name)
Agnieszka Krzysztof Krzysztof Agnieszka
Otrzymaliśmy taki sam wynik, jednakże utraciliśmy enkapsulacje (w tym przypadku jej nie potrzebowaliśmy). W późniejszym czasie okazuje się jednak, że chcemy dodać logikę ustawiania, więc chcemy zapewnić także enkapsulacje. Kolejny przykład: zakładamy, że jeśli imię to “Agnieszka” – ustawiamy “beautiful”, jeśli “Krzysztof” – “ugly”, a w przypadku braku “Agnieszka” oraz “Krzysztof” ustawiamy pole na “CodeCouple”:
def __init__(self, name): self.set_name(name) def get_name(self): return self.__name def set_name(self, name): if name == 'Agnieszka': self.__name = 'beautiful' elif name == 'Krzysztof': self.__name = 'ugly' else: self.__name = 'CodeCouple'
agnieszka = CodeCouple('Agnieszka') print(agnieszka.get_name()) krzysztof = CodeCouple('Krzysztof') print(krzysztof.get_name()) code_couple = CodeCouple('UglyKrzysztof') print(code_couple.get_name())
Jak widać wszystko działa poprawnie, jednakże ludzie korzystają najczęściej z wywołania zaprezentowanego wcześniej czyli:
krzysztof.name = 'Krzysztof'
Jak zapewnić enkapsulacje z jednoczesnym wywołaniem w stylu zmienna.pole = ‘wartość’? Odpowiedzieć to dekoratory properties, czyli mechanizm wbudowany w Pythona. Przepiszemy wcześniejszy przykład, aby pokazać zastosowanie dekoratora @property:
class CodeCouple(object): def __init__(self, name): self.name = name @property def name(self): return self.__name @name.setter def name(self, name): if name == 'Agnieszka': self.__name = 'beautiful' elif name == 'Krzysztof': self.__name = 'ugly' else: self.__name = 'CodeCouple'
agnieszka = CodeCouple('Agnieszka') print(agnieszka.name) krzysztof = CodeCouple('Krzysztof') print(krzysztof.name) code_couple = CodeCouple('UglyKrzysztof') print(code_couple.name)
beautiful ugly CodeCouple
Jak widzicie wynik jest ten sam, ale zachowana jest enkapsulacja oraz wywołanie w pożądanym stylu. Aby osiągnąć ten efekty należy użyć dekoratora @property, który identyfikuje metodę jako getter. Aby dodać setter należy użyć @name.setter, gdzie name musi być takie samo jak nazwa pola. Dekorator @property ma poważną wadę: niepowiązane ze sobą klasy nie mają możliwości współdzielenia tej samej implementacji. Aby rozwiązać ten problem powstały deskryptory, ale o tym innym razem!