Python setter i gettery

0

Poniżej mam kod. Czy aby użyć getterow i setterow dla innych zmiennych obiektu muszę na nowo stworzyć dekoratory i funkcje jak to zrobiłem dla marki? Czy jest może inny sposób-szybszy?
Pozdriawiam

class Car:
    def __init__(self, marka, rocznik):
        self.marka = marka
        self.rocznik = rocznik

    @property
    def marka(self):
        return self.__marka

    @marka.setter
    def marka(self, nazwa):
        self.__marka = nazwa

    @marka.deleter
    def marka(self):
        del self.marka

    def __repr__(self):
        return "{}, {}".format(self.marka, self.rocznik)
auto = Car("Audi", 2019)

print(auto.marka)
auto.marka = "BMW"
print(auto.marka)
print(auto)
0

A po co te property jak nic w nich nie robisz. To nie java. A nawet w javie to jest kontrowersyjne (tylko tam niestety nie ma property)

0

Tymczasem "Pythonic" będzie nie używać getterów i setterów, tylko ustawiać bezpośrednio atrybuty (plain attributes).
https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance

0

Poproszę kogoś o poprawę kodu, abym wiedział jak to ma wyglądać.

1

Raz Nazywasz atrybut self.marka, a potem self.__marka, to Popraw. A mając obiekt, nie widzę potrzeby usuwania i zmieniania jego atrybutów (bo jak tak to po co mi obiekt? Wystarczy lista albo słownik).

2

Robisz 2 błędy.

  1. Pierwszy logiczny. Po co samochód miałby zmieniać swoją markę? Takie zachowanie w ogóle mija się z rzeczywistością. Ostatecznie użyłbym settera, by właśnie rzucić wyjątek jeśli ktoś czegoś takiego spróbuje dokonać (chociaż i tak lepiej byłoby użyć tutaj zamrożony dataclass), ale popatrz pierw na drugi punkt.
  2. Python mimo, że jest językiem obiektowym i pozwala stosować praktyki obiektowe. Jednak ostatecznie za bardzo mu nie służą konstrukcje, które właściwie nic nie robią poza komplikowaniem kodu. W javie to standard, a w pythonie ludzie pomyślą, że masz problem z głową.

Bardzo rzadko idzie spotkać osobę, która ma wyważone podejście z klasami w pythonie (tzn zna i rozumie ograniczenia jakie wiążą się z różnymi technikami). Ludzie na ogół małpują tzn. często nadużywają dziedziczenia (choćby z powodu django, jak i własnych paranoi), klasy abstrakcyjne, interfejsy, piszą bardzo generyczny kod, a jeszcze niektórzy zapędzają się w metaprogramowanie. Ostatecznie mają z tym więcej problemów niż ludzie płaskomyślący, którzy piszą kod bez abstrakcji z nastawieniem na modyfikacje, lub przepisywaniem fragmentów kodu od nowa.

4

Rozłóżmy zagadnienie na czynniki pierwsze. Najpierw zdefiniujmy najprostszą postać klasy Car:

class Car:

    def __init__(self, brand, year_production):
       self.brand = brand
       self.year_production = year_production

    def __str__(self):
        return "{0} was produced in {1}".format(self.brand, self.year_production)

Przejdźmy do wykorzystania klasy:

my_car = Car("MINI Cooper", 2018)
print(my_car)
# MINI Cooper was produced in 2018
print(my_car.brand)
# MINI Cooper
print(my_car.year_production)
# 2018
my_car.year_production = 2019
print(my_car.year_production)
# 2019

Widzimy, że możemy zmienić bardzo łatwo rok produkcji samochodu, co jest w realnym życiu niemożliwe. Rok produkcji powinien być niezmienny, zresztą podobnie jak marka samochodu (nie zrobisz z Malucha Poloneza i odwrotnie). Chcielibyśmy, aby nie dało się tej wartości zmienić. Możemy więc ukryć te atrybuty (marka, rok produkcji) i stworzyć metodę, która będzie służyć do ich odczytywania:

class Car:

    def __init__(self, brand, year_production):
       self.__brand = brand
       self.__year_production = year_production

    def brand(self):
        return self.__brand

    def year_production(self):
        return self.__year_production

    def __str__(self):
        return "{0} was produced in {1}".format(self.__brand, self.__year_production)

Teraz możemy stworzyć nowy obiekt i odczytać jego wartości:

my_car = Car("BMW", 2010)
print(my_car.brand())
# BMW
print(my_car.year_production())
# 2010

Przy okazji zauważmy, że musimy ukryć atrybuty, dodając prefiks w postaci __ przed nazwą zmiennej. Gdyby tego nie zastosować, to wciąż można by zmienić atrybuty z pomocą zwykłego przypisania, tj. my_car.brand = "Opel". Z powyższego kodu widzimy również, że aby pobrać atrybut, musimy wywołać metodę, a więc dodać na końcu parę (, ), np. my_car.brand(). Takie rozwiązanie niekoniecznie jest eleganckie. Lepiej byłoby nie używać nawiasów. W takim przypadku możemy wykorzystać dekorator @property:

class Car:

    def __init__(self, brand, year_production):
       self.__brand = brand
       self.__year_production = year_production

    @property
    def brand(self):
        return self.__brand

    @property
    def year_production(self):
        return self.__year_production

    def __str__(self):
        return "{0} was produced in {1}".format(self.__brand, self.__year_production)

Teraz możemy pobierać prywatną wartość i nie możemy jej ponownie ustawić, bo program rzuci wyjątkiem:

my_car = Car("Opel", 2007)
print(my_car.year_production)
# 2007
my_car.year_production = 2012
# AttributeError: can't set attribute
print(my_car)
# Opel was produced in 2007

Ostatnia wersja klasy wydaje się więc tym, czego szukałeś. Ustawiasz atrybuty obiektu tylko raz w konstruktorze i to się nie może zmienić. Dodatkowo wystawiasz publiczny interfejs w postaci metod, które zwracają Ci odpowiednie wartości atrybutów. Z pomocą @property pozbywasz się brzydkich nawiasów. Ale to koniec. Załóżmy, że udostępniłeś taką klasę, np. publikując ją na Python Package Index (odradzam, bo to trywialna klasa). Po jakimś czasie pisze do Ciebie znajomy, że przecież to bezsensu, że wyprodukowano samochód w 1642 roku, bo wtedy jeszcze nie było samochodów. Sprawdzasz na Wikipedii, że pierwsza masowa produkcja aut rozpoczęła się w 1903 roku (Ford). Chciałbyś to jakoś uwzględnić w swoim kodzie, który już wykorzystują inni. Na szczęście dekorator @property daje Ci taką możliwość. Z jego pomocą możesz przerobić klasę tak, by ustawić setter:

class Car:

    def __init__(self, brand, year_production):
       self.__brand = brand
       self._year_production = year_production

    @property
    def brand(self):
        return self.__brand

    @property
    def year_production(self):
        return self.__year_production

    @year_production.setter
    def _year_production(self, year):
        first_car_year_production = 1903

        if year < first_car_year_production:
            raise ValueError("Year of production must be >= {}".format(first_car_year_production))
        else:
            self.__year_production = year

    def __str__(self):
        return "{0} was produced in {1}".format(self.__brand, self.__year_production)

Zauważmy, że metodzie __init__ zmieniliśmy ostatnią linię. Wywołuje ona wewnętrzną metodę _year_production(self, year). Mogliśmy zostawić year_production(self, year), bo dzięki @property możemy przeciążać tę metodę (drugą metodą jest getter year_production(self)), ale prefiks _ sugeruje, że ta metoda jest metodą wewnętrzną i nie powinna być używana przez inne obiekty/programy/użytkowników. Dodatkowo jest ona oznaczona za pomocą dekoratora jako setter dla zmiennej year_production.

Teraz już możesz tworzyć obiekty typu Car. Jeśli ustawisz nieprawidłowy rok produkcji, to program zrzuci wyjątek. Atrybuty pobierasz z pomocą metod, ale dekorator @property zmienia je na zwykłe atrybuty. I jeszcze jedno, metoda __repr__ służy do czegoś innego niż __str__. Poczytaj sobie o różnicach. W każdym razie w Twoim przypadku lepiej użyć __str__, co też uczyniłem.

4

Ja chyba jednak napisałbym to tak...

from dataclasses import dataclass

@dataclass(frozen=True)
class Car:
    brand: str
    year: int

    def __str__(self):
        return f'{self.brand} was produced in {self.year}'

car = Car("Opel", 2007)
print(car)

Edytka:
W komentarzu @Guaz słusznie zauważył, że to śmiga na 3.7+ zatem gdyby ktoś był skazany na Pythona 2.4+ to można to napisać też z użyciem namedtuple. W takim wypadku niemutowalność jest jedyną opcją, a nie wyborem:

from collections import namedtuple

class Car(namedtuple('Car', ['brand', 'year'])):
	
	def __str__(self):
		return '{0} was produced in {1}'.format(self.brand, self.year)
        
car = Car('Opel', 2007)
print(car)

1 użytkowników online, w tym zalogowanych: 0, gości: 1