Mam nadzieję, że to pytanie nie zostało wcześniej zadane, mój Google / SX-FU nie jest zbyt dobry na tym, ponieważ mógłbym nie znać odpowiednich słów kluczowych.

Załóżmy, że mam klasę, która reprezentuje raczej złożony obiekt, np. sol. Chmura punktu, która ma pewne właściwości (długość, objętość ...). Zazwyczaj udałbym się o określanie klasy chmury punktowej (lub w tym przypadku, prostokątnym), podobnie temu (przykład uprzejmości Samouczek Pythona dla początkujących zmodyfikowany):

class Rectangle:
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def area(self):
        return self.x * self.y
    def perimeter(self):
        return 2 * self.x + 2 * self.y

I kiedy muszę znać obszar prostokąta, po prostu zadzwonię my_rectangle.area(), co zawsze da mi właściwy wynik, nawet jeśli wymiary zmiany prostokąta.

Teraz w mojej aplikacji obliczanie obwodu lub obszaru jest o wiele bardziej złożone i zajmuje sporo czasu. Również, zazwyczaj muszę poznać obwód częściej niż modyfikuję obiekt. Więc miałoby to sens, aby podzielić obliczenie z dostępu do samej wartości:

class Rectangle:
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def calc_area(self):
        self.area = self.x * self.y
    def calc_perimeter(self):
        self.perimeter = 2 * self.x + 2 * self.y

Teraz, jeśli muszę znać obszar prostokąta, muszę zadzwonić my_rectangle.calc_area() przynajmniej raz po każdej modyfikacji, ale potem zawsze mogę po prostu uzyskać my_rectangle.area.

Czy to dobry pomysł czy powinien raczej utrzymywać obliczenie obszaru w metodzie .area() i dostępu, gdy potrzebuję go, podczas przechowywania bieżącego obszaru w zmiennej lokalnej w której skrypt jest używany klasa My Rectangle ?

Jeśli jest to zbyt ogólne oparte lub zbyt uzależnione od rzeczywistej aplikacji, proszę doradzić w sprawie poprawy pytania.

2
Raketenolli 26 czerwiec 2017, 12:36

4 odpowiedzi

Najlepsza odpowiedź

Tak, przynajmniej zasadę. Jeśli warto buforować, obliczenia z drugiej strony jest inaczej pytanie.

Istnieją co najmniej przypadki, w których atrybuty lub właściwości są obliczane przy pierwszym czasie są używane w standardowej bibliotece - a co jest w standardowej bibliotece powinna być prawdopodobnie uznana za Pythonic. Etap stamtąd użyć go do nieruchomości, który może się zmienić w trakcie życia obiektu, nie należy uważać za zbyt daleko (ale wtedy musisz pamiętać, aby unieważnić wartość buforowanej, gdy trzeba go zmienić).

Jednak w takich przypadkach, używają dekoratora @property, aby uzyskać dostęp do nieruchomości, jakby był atrybut. Również jeśli zależy od innych atrybutów, mamy jeszcze więcej powodów, aby użyć właściwości:

class Rectangle(object):
    def __init__(self, w, h):
        self._width = w
        self._height = h

    @property 
    def width(self):
        return self._width

    @width.setter
    def width(self, w):
        self._width = w 
        del self._area

    @property 
    def height(self):
        return self._height

    @height.setter
    def height(self, w):
        self._height = w 
        del self._area

    @cached
    def area(self):
        return self._width * self._height

Uwaga del self._area w settera dla self.width, który dokona następnego dostępu do .area, aby wymagać ponownego obliczenia self._area.

W tym konkretnym przypadku możesz ustawić self._area do None i sprawdzić, co w innych odpowiedziach. Jednak ta technika może nie działać, jeśli atrybut może wynosić None jako prawidłowej wartości. Metoda try - {X4}} może obsługiwać tę możliwość. Aby uzyskać więcej ogólnego podejścia, możesz zdefiniować swój własny dekorator:

def cached(calc):
    def getter(self):
        key = "_" + calc.__name__

        try:    
            return getattr(self, key)
        except AttributeError:
            val = calc(self)
            setattr(self, key, val)
            return val
    return property(getter)

A następnie w definicji Rectangle zamiast zdefiniował area jako:

    @cached
    def area(self):
        return self._width * self._height
2
skyking 29 czerwiec 2017, 06:10

Cóż, to naprawdę pytanie "piythonic / nie pytającego". Pytasz o projektowanie: do pamięci podręcznej lub nie do pamięci.

To, co mam obliczyć obszar i obwód w metodzie __init__. A jeśli twoja klasa będzie się zmientalna (zmodyfikujesz x i y z zewnątrz), będziesz chciał użyć osadnicy, w którym powinno również zaktualizować obszar i obwód.

Należy również pamiętać, że jest to klasyczny procesor procesora / RAM, więc użyj go tylko wtedy, gdy często otrzymasz obszar i obwód często, więc spowodowałoby różnicę prędkości.

0
Meloman 26 czerwiec 2017, 09:56

Po pierwsze, wydaje się to dobrym użyciem dla nieruchomości:

...
@property
def area(self):
    ...

Następnie możesz uzyskać dostęp do my_rectangle.area.

Jeśli obliczanie obszaru jest długi procesem i nie chcesz, aby użytkownicy klasowi przeszkadza sobie z nim, oblicz ją po raz pierwszy, do którego dostępna jest nieruchomość area. W kolejnych dostępach zwróć obliczoną wartość.

Jeśli coś w obiekcie zmienia się i obszar musi zostać ponownie obliczony, po prostu zaznacz obszar, jak nie jest obliczany, gdy X lub Y Zmiana (możesz je zrobić @property S)

1
zmbq 26 czerwiec 2017, 10:09

Właściwości są rzeczywiście sposobem na przejście tutaj. Sugerowałbym coś wzdłuż linii:

class Rectangle:
    def __init__(self, x, y):
        # member names starting with underscore indicate private access
        self._x = x
        self._y = y

        # it's good practice to initialize all members in __init__
        self._area = None

    @property
    def area(self):
        # this is a read-only property.
        # access it like:
        #   rect = Rectangle(1, 1)
        #   print(rect.area)
        # note the missing parentheses.
        if self._area is None:
            # lengthy computation here, but only when needed
            self._area = self._x * self._y
        return self._area

    @property
    def x(self):
        # getter for self._x
        return self._x

    @x.setter
    def x(self, value):
        # setter for self._x
        self._x = value
        # invalidate self._area - it will get recalculated on next access
        self._area = None

    # getters and setters for y are similar.
3
Andreas Grapentin 26 czerwiec 2017, 10:34