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?
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.
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.
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.
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.
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__
lubsetattr
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.
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 we.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.
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.
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.
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.
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.
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.
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()
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
typuFoo
, który ma elementnum
. 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
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