Python – gettery i settery – @property

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!

  • drobna zmiana, żeby kod działał –> inicjalizujemy __name, a nie name 😉

    def __init__(self, name):
    self.__name = name