Używanie @ property a gettery i settery

Oto pytanie projektowe dla Pythona:

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

I

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

Python pozwala nam to zrobić tak czy inaczej. Gdybyś zaprojektował program Pythona, jakiego podejścia byś użył i dlaczego?

Author: martineau, 2011-07-08

12 answers

preferuj właściwości . Po to tam są.

Powodem jest to, że wszystkie atrybuty są publiczne w Pythonie. Rozpoczynanie nazw z podkreśleniem lub dwoma jest tylko ostrzeżeniem, że dany atrybut jest szczegółem implementacji, który może nie pozostać taki sam w przyszłych wersjach kodu. Nie uniemożliwia to uzyskania lub ustawienia tego atrybutu. Dlatego standardowy dostęp do atrybutów jest normalnym Pythonicznym sposobem dostępu do atrybutów.

Korzyść właściwości jest to, że są one składniowo identyczne do atrybutu access, więc można zmieniać z jednego do drugiego bez żadnych zmian w kodzie klienta. Możesz mieć nawet jedną wersję klasy, która używa właściwości (powiedzmy, dla kodu według umowy lub debugowania) i taką, która nie dla produkcji, bez zmiany kodu, który jej używa. W tym samym czasie nie musisz pisać getterów i setterów do wszystkiego na wypadek, gdybyś musiał później lepiej kontrolować dostęp.

 552
Author: kindall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-05 19:25:05

W Pythonie nie używasz getterów, setterów czy właściwości tylko dla Zabawy. Najpierw używasz atrybutów, a później, tylko w razie potrzeby, migrujesz do właściwości bez konieczności zmiany kodu za pomocą klas.

Jest rzeczywiście dużo kodu z rozszerzeniem. py, który używa getterów i setterów, dziedziczenia i bezsensownych klas wszędzie tam, gdzie zrobiłaby to np. prosta krotka, ale jest to kod od ludzi piszących w C++ lub Javie za pomocą Pythona.

To nie Kod Pythona.

 139
Author: 6502,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-07 23:08:18

Używanie właściwości pozwala zacząć od zwykłych dostępu do atrybutów, a następnie wykonać ich kopię zapasową za pomocą getterów i setterów.

 113
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-07 22:57:27

Krótka odpowiedź brzmi: właściwości wygrywa ręce w dół. Zawsze.

Czasami jest potrzeba getterów i seterów, ale nawet wtedy "ukrywałbym" je przed światem zewnętrznym. Istnieje wiele sposobów, aby to zrobić w Pythonie (getattr, setattr, __getattribute__, itd..., ale bardzo zwięzłe i czyste to:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Oto krótki artykuł , który wprowadza temat getterów i setterów w Pythonie.

 66
Author: mac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-20 22:51:27

[TL; DR? możesz przejść do końca, aby zobaczyć przykład kodu .]

Właściwie wolę używać innego idiomu, który jest trochę zaangażowany w używanie jako jednorazowy, ale jest miły, jeśli masz bardziej złożony przypadek użycia.

Najpierw trochę tła.

Właściwości są użyteczne, ponieważ pozwalają nam obsługiwać zarówno ustawianie, jak i pobieranie wartości w sposób programowy, ale nadal umożliwiają dostęp do atrybutów jako atrybutów. Możemy zamienić "gets" w "obliczenia" (zasadniczo) i możemy zamienić "Zestawy" W "zdarzenia". Załóżmy więc, że mamy następującą klasę, którą zakodowałem za pomocą getterów i setterów podobnych do Javy.

class Example(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

Możesz się zastanawiać, dlaczego nie wywołałem defaultX i defaultY w metodzie obiektu __init__. Powodem jest to, że w naszym przypadku chcę założyć, że metody someDefaultComputation zwracają wartości, które zmieniają się w czasie, np. znacznik czasu, a gdy x (lub y) nie jest ustawione (gdzie, dla celów tego przykładu, "not set" oznacza "set to None") chcę wartość domyślnych obliczeń x'S (lub y's).

Więc jest to lamerskie z wielu powodów opisanych powyżej. Przepiszę go używając właściwości:

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.
Co zyskaliśmy? Zyskaliśmy możliwość odwoływania się do tych atrybutów jako do atrybutów, mimo że za kulisami kończymy na uruchamianiu metod.

Oczywiście rzeczywistą siłą właściwości jest to, że generalnie chcemy, aby te metody robiły coś oprócz tylko uzyskiwania i ustawianie wartości (w przeciwnym razie nie ma sensu używać właściwości). Zrobiłem to w moim przykładzie getter. Zasadniczo uruchamiamy ciało funkcji, aby podnieść wartość domyślną, gdy wartość nie jest ustawiona. Jest to bardzo powszechny wzór.

Ale co tracimy, a czego nie możemy zrobić?

Główną irytacją, moim zdaniem, jest to, że jeśli zdefiniujesz getter (tak jak my tutaj), musisz również zdefiniować setter.[1] to dodatkowy hałas, który zaśmieca kod.

Kolejną irytacją jest to, że my nadal trzeba zainicjalizować wartości x i y W __init__. (Cóż, oczywiście możemy je dodać używając setattr(), ale to jest bardziej dodatkowy kod.)

Po trzecie, w przeciwieństwie do przykładu podobnego do Javy, gettery nie mogą akceptować innych parametrów. Teraz słyszę, jak już mówisz, cóż, jeśli to bierze parametry, to nie jest getter! W oficjalnym sensie, to prawda. Ale w sensie praktycznym nie ma powodu, abyśmy nie mogli parametryzować nazwanego atrybutu -- jak x -- i ustawić jego wartość dla niektórych określonych parametrów.

Byłoby miło, gdybyśmy mogli zrobić coś takiego:

e.x[a,b,c] = 10
e.x[d,e,f] = 20

Na przykład. Najbliższy, jaki możemy uzyskać, to nadpisanie przypisania, aby implikować jakąś specjalną semantykę:

e.x = [a,b,c,10]
e.x = [d,e,f,30]

I oczywiście upewnij się, że nasz seter wie, jak wyodrębnić pierwsze trzy wartości jako klucz do słownika i ustawić jego wartość na liczbę lub coś w tym stylu.

Ale nawet gdybyśmy to zrobili, nadal nie moglibyśmy wspierać go właściwościami, ponieważ nie ma sposobu, aby uzyskać wartość, ponieważ nie możemy w ogóle przekazać parametrów do gettera. Więc musieliśmy wszystko zwrócić, wprowadzając asymetrię.

Getter/setter w stylu Java pozwala nam się tym zająć, ale wracamy do potrzeb getter / setter.

W moim umyśle to, czego naprawdę chcemy, to coś, co uchwyci następujące wymagania:]}

  • Użytkownicy definiują tylko jedną metodę dla danego atrybutu i mogą tam wskazywać czy atrybut jest tylko do odczytu czy do odczytu. Właściwości fail ten test jeśli atrybut zapisywalny.

  • Nie ma potrzeby, aby użytkownik definiował dodatkową zmienną leżącą u podstaw funkcji, więc nie potrzebujemy __init__ lub setattr w kodzie. Zmienna istnieje tylko dzięki temu, że stworzyliśmy ten atrybut Nowego Stylu.

  • Każdy domyślny kod atrybutu jest wykonywany w samej metodzie.

  • Możemy ustawić atrybut jako atrybut i odwołać się do niego jako atrybut.

  • Możemy sparametryzować atrybut.

Jeśli chodzi o kod, chcemy napisać:

def x(self, *args):
    return defaultX()

I być w stanie wtedy zrobić:

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

I tak dalej.

Chcemy również, aby to zrobić dla specjalnego przypadku parametryzowalnego atrybutu, ale nadal zezwalamy na działanie domyślnego przypisania przypadku. Zobaczysz, jak sobie z tym poradziłem poniżej.

A teraz do rzeczy (yay! punkt!). Rozwiązanie, które wymyśliłem dla tego jest jak / align = "left" /

Tworzymy nowy obiekt, który zastąpi pojęcie własności. Obiekt jest przeznaczony do przechowywania wartości zmiennej ustawionej na nim, ale również utrzymuje uchwyt na kodzie, który wie, jak obliczyć wartość domyślną. Jego zadaniem jest przechowywanie zestawu value lub uruchomienie method, jeśli ta wartość nie jest ustawiona.

Nazwijmy to UberProperty.

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

Zakładam, że method tutaj jest metoda klasy, value jest wartością UberProperty, i dodałem isSet, ponieważ None może być rzeczywistą wartość i to pozwala nam czysty sposób zadeklarować, że naprawdę nie ma wartości. Innym sposobem jest jakiś strażnik.

To w zasadzie daje nam obiekt, który może robić to, co chcemy, ale jak właściwie umieścić go na naszej klasie? Cóż, nieruchomości używają dekoratorów, dlaczego nie możemy? Zobaczmy, jak to może wyglądać(od teraz będę się trzymał używania tylko jednego 'atrybutu', x).

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

To jeszcze nie działa, oczywiście. Musimy wdrożyć uberProperty i upewnij się, że obsługuje zarówno gets, jak i sets.

Zacznijmy od gets.

Moja pierwsza próba polegała na stworzeniu nowego obiektu UberProperty i zwróceniu go:]}
def uberProperty(f):
    return UberProperty(f)

Szybko odkryłem, oczywiście, że to nie działa: Python nigdy nie wiąże wywoływalnego obiektu i potrzebuję go, aby wywołać funkcję. Nawet tworzenie dekoratora w klasie nie działa, ponieważ chociaż teraz mamy klasę, nadal nie mamy przedmiotu do pracy.

Więc idziemy do trzeba być w stanie zrobić więcej tutaj. Wiemy, że metoda musi być reprezentowana tylko raz, więc zachowajmy nasz dekorator, ale zmodyfikuj UberProperty, aby przechowywać tylko {35]} odniesienie: {72]}

class UberProperty(object):

    def __init__(self, method):
        self.method = method
Nie można go też nazwać, więc w tej chwili nic nie działa.

Jak uzupełnić obraz? Cóż, co się kończy, gdy tworzymy klasę przykładową używając naszego nowego dekoratora: {]}

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

W obu przypadkach odzyskujemy UberProperty co oczywiście nie jest możliwe do wywołania, więc to się nie przyda.

Potrzebujemy w jakiś sposób dynamicznie powiązać instancję UberProperty utworzoną przez dekorator po utworzeniu klasy z obiektem klasy, zanim ten obiekt zostanie zwrócony do tego użytkownika w celu użycia. Um, tak, to jest __init__ telefon, koleś.

Napiszmy, co chcemy, aby nasz wynik był pierwszy. Wiążemy UberProperty z instancją, więc oczywistą rzeczą do powrotu będzie BoundUberProperty. To tutaj będziemy zachowaj stan dla atrybutu x.

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

Teraz mamy reprezentację; jak dostać to do obiektu? Istnieje kilka podejść, ale najprostszym do wyjaśnienia jest użycie metody __init__ do tego mapowania. Do czasu {[21] } jest nazywany nasz dekorator uruchomiony, więc wystarczy przejrzeć obiekt __dict__ i zaktualizować wszelkie atrybuty, gdzie wartość atrybutu jest typu UberProperty.

Teraz, Uber-właściwości są fajne i prawdopodobnie będziemy chcieli z nich korzystać dużo, więc to ma sens, aby po prostu utworzyć klasę bazową, która robi to dla wszystkich podklas. Myślę, że wiesz, jak będzie się nazywać podstawowa klasa.

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

Dodajemy to, zmieniamy nasz przykład na dziedziczenie z UberObject i ...

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

Po modyfikacji x na:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

Możemy wykonać prosty test:

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

I otrzymujemy wyjście, które chcieliśmy:

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(rany, pracuję do późna.)

Zauważ, że użyłem getValue, setValue, i clearValue tutaj. To to dlatego, że jeszcze nie podłączyłem się do środków, aby te automatycznie wróciły.

Ale myślę, że to dobre miejsce, żeby się zatrzymać, bo zaczynam się męczyć. Możesz również zobaczyć, że podstawowa funkcjonalność, którą chcieliśmy, jest na miejscu; reszta to window dressing. Ważne usability window dressing, ale to może poczekać, aż będę miał zmianę, aby zaktualizować post.

Dokończę przykład w następnym wpisie, odnosząc się do tych rzeczy:

  • Musimy upewnij się, że __init__ UberObject jest zawsze wywoływany przez podklasy.

    • więc albo wymusimy to gdzieś, albo zapobiegniemy jego implementacji.
    • zobaczymy, jak to zrobić z metaklasą.
  • Musimy się upewnić, że zajmiemy się powszechną sprawą, w której ktoś 'aliasuje' funkcja do czegoś innego, np.:

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
    
  • Musimy e.x domyślnie zwracać e.x.getValue().

    • co właściwie zobaczymy czy jest to jeden z obszarów, w którym model zawodzi.
    • okazuje się, że zawsze będziemy musieli użyć wywołania funkcji, aby uzyskać wartość.
    • ale możemy sprawić, by wyglądało to jak zwykłe wywołanie funkcji i uniknąć konieczności używania e.x.getValue(). (Zrobienie tego jest oczywiste, jeśli jeszcze go nie naprawiłeś.)
  • Musimy wspierać ustawienie e.x directly, jak w e.x = <newvalue>. Możemy to zrobić również w klasie nadrzędnej, ale będziemy musieli zaktualizować nasz kod __init__, aby obsłużyć to.

  • Na koniec dodamy sparametryzowane atrybuty. To powinno być dość oczywiste, jak to zrobimy.

Oto kod, jaki istnieje do dziś:

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] Mogę być w tyle, czy tak jest nadal.

 56
Author: Adam Donahue,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-25 17:03:44

Myślę, że oboje mają swoje miejsce. Jednym z problemów z używaniem @property jest to, że trudno jest rozszerzyć zachowanie getterów lub setterów w podklasach przy użyciu standardowych mechanizmów klas. Problem polega na tym, że rzeczywiste funkcje getter/setter są ukryte we właściwości.

Można rzeczywiście uzyskać funkcje, np. za pomocą

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

Możesz uzyskać dostęp do funkcji getter i setter jako C.p.fget i C.p.fset, ale nie możesz łatwo użyć funkcji dziedziczenia normalnych metod (np. oni. Po pewnym zagłębieniu się w zawiłości super, możesz rzeczywiście używać super w ten sposób:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

Używanie super() jest jednak dość niezgrabne, ponieważ właściwość musi zostać przedefiniowana i musisz użyć nieco kontrintuicyjnego mechanizmu super (cls,cls), aby uzyskać niezwiązaną kopię p.

 24
Author: NeilenMarais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-23 09:40:57

Używanie właściwości jest dla mnie bardziej intuicyjne i lepiej pasuje do większości kodu.

Porównanie

o.x = 5
ox = o.x

Vs.

o.setX(5)
ox = o.getX()

Jest dla mnie dość oczywiste, które jest łatwiejsze do odczytania. Również właściwości pozwala na zmienne prywatne znacznie łatwiejsze.

 19
Author: Hobblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-07 22:53:40

W większości przypadków wolałabym użyć żadnego z nich. Problem z właściwościami polega na tym, że czynią one klasę mniej przejrzystą. Szczególnie, jest to problem, jeśli miałbyś podnieść wyjątek od setera. Na przykład, jeśli masz konto.właściwość e-mail:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

Wtedy użytkownik klasy nie spodziewa się, że przypisanie wartości do właściwości może spowodować wyjątek:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

W rezultacie, wyjątek może pozostać nieobsługiwany i albo propagować się zbyt wysoko w łańcuchu połączeń, aby być obsĹ 'ugiwane poprawnie lub skutkujÄ ... bardzo nieprzydatnym Ĺ" ledztwem prezentowanym uĺźytkownikowi programu (co niestety jest zbyt powszechne w Ĺ " wiecie Pythona i Javy).

Ja również unikałbym używania getterów i setterów:

  • ponieważ zdefiniowanie ich dla wszystkich właściwości z góry jest bardzo czasochłonne,
  • sprawia, że ilość kodu niepotrzebnie się wydłuża, co utrudnia zrozumienie i utrzymanie kodu,
  • jeśli zdefiniowano je dla właściwości tylko jako w przeciwieństwie do innych klas, klasa ta nie może być używana przez inne klasy.]}

Zamiast właściwości i getterów/setterów wolę wykonywać skomplikowaną logikę w dobrze zdefiniowanych miejscach, np. w metodzie walidacji:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

Lub podobne konto.metoda zapisu.

Zauważ, że nie próbuję mówić, że nie ma przypadków, gdy właściwości są użyteczne, tylko, że może być lepiej, jeśli możesz uczynić swoje klasy prostymi i przejrzystymi na tyle, że nie potrzebujesz oni.

 12
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-03 09:28:56

Wydaje mi się, że właściwości polegają na umożliwieniu ci pisania getterów i seterów tylko wtedy, gdy naprawdę ich potrzebujesz.

Kultura programowania w Javie zdecydowanie zaleca, aby nigdy nie dawać dostępu do Właściwości, a zamiast tego przechodzić przez gettery i settery i tylko te, które są rzeczywiście potrzebne. To trochę gadatliwe, aby zawsze pisać te oczywiste fragmenty kodu i zauważyć, że 70% czasu nigdy nie są zastępowane przez jakąś nietrywialną logikę.

W Pythonie ludzie faktycznie dbaj o tego rodzaju nad głową, abyś mógł przyjąć następującą praktykę:

  • nie używaj getterów i setterów na początku, jeśli nie są potrzebne
  • Użyj @property, aby zaimplementować je bez zmiany składni reszty kodu.
 9
Author: fulmicoton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-19 16:29:09

Dziwię się, że nikt nie wspomniał o tym, że właściwości są związanymi metodami klasy deskryptora, Adam Donohue i NeilenMarais w swoich postach dokładnie o tym myślą-że gettery i settery są funkcjami i mogą być użyte do:

  • validate
  • Zmień dane
  • Typ kaczki (wymuszenie typu na inny typ)

To prezentuje inteligentny sposób ukrywania szczegółów implementacji i kodowania takich jak wyrażenie regularne, Typ, try .. z wyjątkiem bloków, twierdzeń lub wartości obliczeniowych.

Ogólnie wykonywanie CRUD na obiekcie może być często dość przyziemne, ale rozważ przykład danych, które będą przechowywane w relacyjnej bazie danych. ORM może ukryć szczegóły implementacji poszczególnych języków SQL w metodach powiązanych z fget, fset, fdel zdefiniowanych w klasie właściwości, która będzie zarządzać awful if .. elif .. inne drabiny, które są tak brzydkie w kodzie OO -- odsłaniają proste i eleganckie self.variable = something i usuwają szczegóły dla programista za pomocą ORM.

Jeśli ktoś myśli o właściwościach tylko jako jakiś ponury ślad języka zniewolenia i dyscypliny (np. Java), to pomija punkt deskryptorów.

 8
Author: fiacre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:26

W złożonych projektach wolę używać właściwości tylko do odczytu (lub getterów) z jawną funkcją settera:

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

W długotrwałych projektach debugowanie i refaktoryzacja zajmuje więcej czasu niż pisanie samego kodu. Istnieje kilka wad używania @property.setter, które sprawia, że debugowanie jest jeszcze trudniejsze:

1) python pozwala na tworzenie nowych atrybutów dla istniejącego obiektu. To sprawia, że następujący błąd drukowania jest trudny do wyśledzenia:

my_object.my_atttr = 4.

Jeśli twój obiekt jest skomplikowanym algorytmem, to spędzi sporo czasu próbując dowiedzieć się, dlaczego nie zbiega się (zwróć uwagę na dodatkowe " t " w linii powyżej)

2) setter czasami może ewoluować w skomplikowaną i powolną metodę (np. uderzanie w bazę danych). Byłoby to dość trudne dla innego dewelopera, aby dowiedzieć się, dlaczego poniższa funkcja jest bardzo powolna. Może poświęcić dużo czasu na profilowanie do_something() metoda:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()
 4
Author: otognan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 01:52:28

Zarówno @property jak i tradycyjne getery i setery mają swoje zalety. To zależy od Twojego przypadku użycia.

Zalety @property

  • Nie musisz zmieniać interfejsu podczas zmiany implementacji dostępu do danych. Gdy twój projekt jest mały, prawdopodobnie chcesz użyć bezpośredniego dostępu do atrybutów, aby uzyskać dostęp do członka klasy. Na przykład, załóżmy, że masz obiekt foo typu Foo, który ma element num. Następnie możesz po prostu dostać tego członka za pomocą num = foo.num. W miarę rozwoju projektu możesz mieć wrażenie, że konieczne jest sprawdzenie lub debugowanie prostego dostępu do atrybutów. Następnie można to zrobić z @property w obrębie klasy. Interfejs dostępu do danych pozostaje taki sam, więc nie ma potrzeby modyfikowania kodu klienta.

    Cytowany z PEP-8:

    Dla prostych atrybutów Danych Publicznych, najlepiej jest ujawnić tylko nazwę atrybutu, bez skomplikowanych metod accessor/mutator. Należy pamiętać, że Python zapewnia łatwa ścieżka do przyszłego ulepszenia, jeśli okaże się, że prosty atrybut danych musi rozwijać funkcjonalne zachowanie. W takim przypadku użyj właściwości, aby ukryć implementację funkcjonalną za prostą składnią dostępu do atrybutów danych.

  • Używanie @property dla dostępu do danych w Pythonie jest traktowane jako Pythonic :

    • Może wzmocnić twoją autoidentyfikację jako programisty Pythona (Nie Javy).

    • Może pomóc w rozmowie kwalifikacyjnej, jeśli twój ankieter uważa, że gettery i settery w stylu Java są anty-patterns .

Zalety tradycyjnych getterów i seterów

  • Tradycyjne gettery i settery pozwalają na bardziej skomplikowany dostęp do danych niż prosty dostęp do atrybutów. Na przykład, kiedy ustawiasz członka klasy, czasami potrzebujesz flagi wskazującej, gdzie chcesz wymusić tę operację, nawet jeśli coś nie wygląda idealnie. Chociaż nie jest oczywiste, jak aby zwiększyć bezpośredni dostęp członków, możesz łatwo zwiększyć swój tradycyjny seter o dodatkowy parametr force:

    def Foo:
        def set_num(self, num, force=False):
            ...
    
  • Tradycyjne gettery i settery sprawiają, że jawne że dostęp członka klasy odbywa się za pomocą metody. Oznacza to:

    • To, co otrzymujesz w rezultacie, może nie być takie samo, jak to, co jest dokładnie przechowywane w tej klasie.

    • Nawet jeśli dostęp wygląda jak prosty dostęp do atrybutów, wydajność może się od tego znacznie różnić.

    O ile użytkownicy klas nie oczekują @property ukrywania się za każdą instrukcją dostępu do atrybutów, wyrażanie takich wyrażeń może pomóc zminimalizować niespodzianki użytkowników klas.

  • Jak wspomniano przez @NeilenMarais i w ten post, Rozszerzanie tradycyjnych getterów i setterów w podklasach jest łatwiejsze niż Rozszerzanie właściwości.

  • Tradycyjne gettery i setery są szeroko stosowane przez długi czas w różnych językach. Jeśli w Twoim zespole są ludzie z różnych środowisk, wyglądają bardziej znajomo niż @property. Ponadto, w miarę rozwoju projektu, jeśli będziesz musiał przeprowadzić migrację z Pythona do innego języka, który nie ma @property, użycie tradycyjnych getterów i setterów sprawi, że migracja będzie płynniejsza.

Caveats

  • Ani @property ani tradycyjne gettery i settery nie czynią członka klasy prywatną, nawet jeśli używasz podwójnego podkreślenia przed jego nazwa:

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0
    
 1
Author: Cyker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-06 11:25:32