Preferuj właściwości . Po to są.
Powodem jest to, że wszystkie atrybuty są publiczne w Pythonie. Rozpoczynanie nazw od podkreślenia lub dwóch to tylko ostrzeżenie, że dany atrybut jest szczegółem implementacji, który może nie pozostać taki sam w przyszłych wersjach kodu. Nie przeszkadza ci to w uzyskaniu lub ustawieniu tego atrybutu. Dlatego standardowy dostęp do atrybutów jest normalnym, Pythonicznym sposobem dostępu do atrybutów.
Zaletą właściwości jest to, że są one identyczne pod względem składniowym z dostępem do atrybutów, dzięki czemu można zmieniać je bez żadnych zmian w kodzie klienta. Możesz mieć nawet jedną wersję klasy, która używa właściwości (na przykład do kodowania według umowy lub debugowania) i taką, która nie jest przeznaczona do produkcji, bez zmiany kodu, który z niej korzysta. Jednocześnie nie musisz pisać programów pobierających i ustawiających wszystko, na wypadek gdybyś później musiał lepiej kontrolować dostęp.
W Pythonie nie używasz programów pobierających, ustawiających ani właściwości tylko dla zabawy. Najpierw używasz atrybutów, a później, tylko w razie potrzeby, ostatecznie migrujesz do właściwości bez konieczności zmiany kodu za pomocą swoich klas.
Rzeczywiście jest dużo kodu z rozszerzeniem .py, który używa getterów i seterów oraz dziedziczenia i bezsensownych klas wszędzie tam, gdzie np. Wystarczyłaby prosta krotka, ale jest to kod od ludzi piszących w C ++ lub Javie przy użyciu Pythona.
To nie jest kod Pythona.
źródło
Korzystanie z właściwości pozwala rozpocząć od normalnego dostępu do atrybutów, a następnie wykonać kopię zapasową za pomocą metod pobierających i ustawiających, jeśli to konieczne .
źródło
Krótka odpowiedź brzmi: właściwości wygrywają . Zawsze.
Czasami istnieje potrzeba osób pobierających i ustawiających, 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 nich jest:Oto krótki artykuł, który wprowadza temat getterów i seterów w Pythonie.
źródło
property
. ( Wygląda to jak zwykłe przypisanie, ale wywołuje funkcję.) Dlatego też „Pythonic” jest w zasadzie bez znaczenia, z wyjątkiem tautologicznej definicji: „Konwencje Pythona to rzeczy, które zdefiniowaliśmy jako Pythonic”.property
funkcja grozi uczynieniem idei pytonicznych aksjomatów niemal bezwartościowymi. Więc pozostaje nam tylko lista kontrolna.self
obiektu , pomocne mogą być jawne ustawienia. Na przykładuser.email = "..."
nie wygląda na to, aby mógł zgłosić wyjątek, ponieważ wygląda jak ustawienie atrybutu, auser.set_email("...")
jednocześnie wyjaśnia, że mogą wystąpić efekty uboczne, takie jak wyjątki.[ TL; DR? Możesz przejść do końca, aby uzyskać przykładowy kod .]
Właściwie wolę używać innego idiomu, który jest trochę zaangażowany w używanie go jako jednorazowego użytku, ale jest fajny, jeśli masz bardziej złożony przypadek użycia.
Najpierw trochę tła.
Właściwości są przydatne, ponieważ pozwalają nam obsługiwać zarówno ustawianie, jak i uzyskiwanie wartości w sposób zautomatyzowany, ale nadal umożliwiają dostęp do atrybutów jako atrybutów. Możemy przekształcić „przekształca się” w „obliczenia” (zasadniczo) i możemy przekształcić „zbiory” w „zdarzenia”. Powiedzmy, że mamy następującą klasę, którą kodowałem przy użyciu programów pobierających i ustawiających podobnych do języka Java.
Być może zastanawiasz się, dlaczego nie zadzwonił
defaultX
idefaultY
się obiektu__init__
metody. Powodem jest to, że w naszym przypadku chcę założyć, żesomeDefaultComputation
metody zwracają wartości, które zmieniają się w czasie, powiedzmy znacznik czasu i zawsze, gdyx
(luby
) nie jest ustawiony (gdzie dla celów tego przykładu „nie ustawiono” oznacza „zestaw” na Brak ”) Chcę wartości domyślnego obliczeniax
(luby
).Jest to więc kiepskie z wielu powodów opisanych powyżej. Przepiszę go za pomocą właściwości:
Co zyskaliśmy? Zdobyliśmy możliwość odwoływania się do tych atrybutów jako atrybutów, mimo że za kulisami używamy metod.
Oczywiście prawdziwą potęgą właściwości jest to, że generalnie chcemy, aby te metody działały oprócz pobierania i ustawiania wartości (w przeciwnym razie nie ma sensu używanie właściwości). Zrobiłem to w moim przykładzie gettera. Zasadniczo uruchamiamy ciało funkcji, aby wybrać wartość domyślną, gdy wartość nie jest ustawiona. Jest to bardzo powszechny wzór.
Ale co tracimy, a czego nie możemy zrobić?
Moim zdaniem główną irytacją jest to, że jeśli definiujesz gettera (tak jak my tutaj), musisz także zdefiniować setera. [1] To dodatkowy szum, który zaśmieca kod.
Inną irytacją jest to, że wciąż musimy inicjować wartości
x
i . (Cóż, oczywiście możemy dodać je za pomocą, ale to jest dodatkowy kod).y
__init__
setattr()
Po trzecie, w przeciwieństwie do przykładu podobnego do Javy, pobierające nie mogą zaakceptować innych parametrów. Teraz słyszę, jak mówisz, cóż, jeśli przyjmuje parametry, to nie jest getter! W oficjalnym sensie to prawda. Ale w sensie praktycznym nie ma powodu, abyśmy nie byli w stanie sparametryzować nazwanego atrybutu - podobnego
x
- i ustawić jego wartość dla niektórych określonych parametrów.Byłoby miło, gdybyśmy mogli zrobić coś takiego:
na przykład. Najbliższe, co możemy uzyskać, to przesłonić przypisanie, aby sugerować jakąś specjalną semantykę:
i oczywiście upewnij się, że nasz ustawiający umie wyodrębnić pierwsze trzy wartości jako klucz do słownika i ustawić jego wartość na liczbę lub coś takiego.
Ale nawet gdybyśmy to zrobili, nadal nie moglibyśmy wesprzeć go właściwościami, ponieważ nie ma sposobu na uzyskanie wartości, ponieważ nie możemy w ogóle przekazać parametrów do gettera. Musieliśmy więc zwrócić wszystko, wprowadzając asymetrię.
Getter / setter w stylu Java pozwala nam sobie z tym poradzić, ale wróciliśmy do potrzeby getter / setter.
Moim zdaniem naprawdę chcemy, aby spełnić następujące wymagania:
Użytkownicy definiują tylko jedną metodę dla danego atrybutu i mogą tam wskazać, czy atrybut jest tylko do odczytu, czy do odczytu i zapisu. Właściwości nie przejdą tego testu, jeśli atrybut można zapisać.
Użytkownik nie musi definiować dodatkowej zmiennej leżącej u podstaw funkcji, więc nie potrzebujemy
__init__
anisetattr
w kodzie. Zmienna istnieje tylko dlatego, że stworzyliśmy ten atrybut nowego stylu.Każdy domyślny kod atrybutu jest wykonywany w samym ciele metody.
Możemy ustawić atrybut jako atrybut i odwołać się do niego jako atrybutu.
Możemy sparametryzować atrybut.
Jeśli chodzi o kod, chcemy sposób na napisanie:
i być w stanie wykonać:
i tak dalej.
Chcemy również tego dokonać w specjalnym przypadku atrybutu parametryzowalnego, ale nadal pozwalamy na działanie domyślnego przypadku przypisania. Zobaczysz, jak sobie z tym poradziłem poniżej.
Teraz do rzeczy (tak! Do rzeczy!). Rozwiązanie, dla którego wpadłem na to, jest następujące.
Tworzymy nowy obiekt, aby zastąpić pojęcie własności. Obiekt przeznaczony jest do przechowywania wartości zestawu zmiennych, ale zachowuje również uchwyt kodu, który wie, jak obliczyć wartość domyślną. Jego zadaniem jest zapisanie zestawu
value
lub uruchomienie,method
jeśli ta wartość nie jest ustawiona.Nazwijmy to an
UberProperty
.Zakładam, że
method
tutaj jest metoda klasowa,value
jest wartościąUberProperty
i dodałem,isSet
ponieważNone
może to być prawdziwa wartość, a to pozwala nam na czysty sposób zadeklarowania, że tak naprawdę „nie ma wartości”. Innym sposobem jest jakiś wartownik.Zasadniczo daje nam to obiekt, który może robić to, co chcemy, ale w jaki sposób możemy umieścić go w naszej klasie? Cóż, właściwości używają dekoratorów; dlaczego nie możemy Zobaczmy, jak to może wyglądać (odtąd będę się trzymał tylko jednego „atrybutu”
x
).Oczywiście to jeszcze nie działa. Musimy wdrożyć
uberProperty
i upewnić się, że obsługuje on zarówno zestawy, jak i zestawy.Zacznijmy od dostaje.
Moja pierwsza próba polegała na utworzeniu nowego obiektu UberProperty i zwróceniu go:
Oczywiście szybko odkryłem, że to nie działa: Python nigdy nie wiąże wywoływanego obiektu z obiektem i potrzebuję go do wywołania funkcji. Nawet tworzenie dekoratora w klasie nie działa, ponieważ chociaż teraz mamy klasę, nadal nie mamy obiektu do pracy.
Musimy więc być w stanie zrobić więcej tutaj. Wiemy, że metoda musi być reprezentowana tylko raz, więc chodźmy dalej i zachowaj dekoratora, ale zmodyfikuj,
UberProperty
aby przechowywać tylkomethod
referencję:Nie można go również wywoływać, więc w tej chwili nic nie działa.
Jak uzupełniamy obraz? Cóż, z czym skończymy, tworząc klasę przykładową za pomocą naszego nowego dekoratora:
w obu przypadkach odzyskujemy to,
UberProperty
co oczywiście nie jest możliwe do wywołania, więc nie ma to większego zastosowania.Potrzebujemy jakiegoś sposobu, aby dynamicznie powiązać
UberProperty
instancję utworzoną przez dekorator po utworzeniu klasy z obiektem klasy, zanim ten obiekt zostanie zwrócony do tego użytkownika do użycia. Um, tak, to__init__
połączenie, koleś.Napiszmy, co chcemy, aby nasz wynik wyszukiwania był pierwszy. Jesteśmy wiążące
UberProperty
do instancji, więc rzeczą oczywistą, aby powrócić byłoby BoundUberProperty. To tutaj faktycznie utrzymamy stanx
atrybutu.Teraz my reprezentacja; jak przenieść je na obiekt? Istnieje kilka podejść, ale najłatwiejszym do wyjaśnienia jest po prostu użycie
__init__
metody do wykonania tego mapowania. Do czasu, gdy__init__
nazywa się to, nasze dekoratory już działają, więc wystarczy przejrzeć obiekty__dict__
i zaktualizować wszystkie atrybuty, których wartość jest typuUberProperty
.Teraz właściwości uber są fajne i prawdopodobnie będziemy chcieli z nich często korzystać, więc sensowne jest po prostu utworzenie klasy podstawowej, która robi to dla wszystkich podklas. Myślę, że wiesz, jak będzie nazywana klasa podstawowa.
Dodajemy to, zmieniamy nasz przykład na dziedziczenie
UberObject
i ...Po zmianie
x
na:Możemy przeprowadzić prosty test:
I otrzymujemy pożądaną wydajność:
(Rany, pracuję do późna.)
Zauważ, że użyłem
getValue
,setValue
iclearValue
tu. Wynika to z faktu, że nie podłączyłem jeszcze środków, aby automatycznie je zwrócić.Ale myślę, że jest to dobre miejsce na teraz, aby się zatrzymać, ponieważ jestem zmęczony. Możesz również zobaczyć, że podstawowa funkcjonalność, której chcieliśmy, jest już dostępna; reszta to dekoracja okien. Ważne opatrunek okna użyteczności, ale to może poczekać, aż będę mieć zmianę, aby zaktualizować post.
Skończę przykład w następnym poście, odnosząc się do następujących rzeczy:
Musimy upewnić się, że UberObject
__init__
jest zawsze wywoływany przez podklasy.Musimy upewnić się, że zajmiemy się powszechnym przypadkiem, w którym ktoś „aliasy” funkcji do czegoś innego, na przykład:
Domyślnie musimy
e.x
wróciće.x.getValue()
.e.x.getValue()
. (Wykonanie tego jest oczywiste, jeśli jeszcze go nie naprawiłeś.)Musimy wesprzeć ustawienie
e.x directly
, jak we.x = <newvalue>
. Możemy to zrobić również w klasie nadrzędnej, ale będziemy musieli zaktualizować nasz__init__
kod, aby go obsłużyć.Na koniec dodamy sparametryzowane atrybuty. To powinno być dość oczywiste, jak to zrobimy.
Oto kod, jaki istnieje do tej pory:
[1] Mogę być opóźniony, czy nadal tak jest.
źródło
return self.x or self.defaultX()
to niebezpieczny kod. Co się stanie kiedyself.x == 0
?__getitem__
metodę zastąpiliśmy . To byłoby dziwne, ponieważ miałbyś wtedy zupełnie niestandardowy python.Myślę, że oboje mają swoje miejsce. Jednym z problemów związanych z używaniem
@property
jest to, że trudno jest rozszerzyć zachowanie modułów pobierających lub ustawiających w podklasach za pomocą standardowych mechanizmów klas. Problem polega na tym, że rzeczywiste funkcje pobierające / ustawiające są ukryte we właściwości.Możesz faktycznie uzyskać funkcje, np. Za pomocą
możesz uzyskać dostęp do funkcji pobierających i ustawiających tak jak
C.p.fget
iC.p.fset
, ale nie możesz łatwo użyć zwykłych metod dziedziczenia metod (np. super) w celu ich rozszerzenia. Po pewnym kopanie w zawiłości super, to może rzeczywiście wykorzystać bardzo w ten sposób:Używanie super () jest jednak dość niezręczne, ponieważ właściwość musi zostać zdefiniowana na nowo i musisz użyć nieco sprzecznego z intuicją mechanizmu super (cls, cls), aby uzyskać niezwiązaną kopię p.
źródło
Korzystanie z właściwości jest dla mnie bardziej intuicyjne i lepiej pasuje do większości kodów.
Porównywanie
vs.
jest dla mnie dość oczywiste i łatwiejsze do odczytania. Również właściwości znacznie ułatwiają prywatne zmienne.
źródło
W większości przypadków wolałbym nie używać żadnego z nich. Problem z właściwościami polega na tym, że powodują one, że klasa jest mniej przezroczysta. Jest to szczególnie problem, jeśli chcesz zgłosić wyjątek od setera. Na przykład jeśli masz właściwość Account.email:
wówczas użytkownik klasy nie oczekuje, że przypisanie wartości do właściwości może spowodować wyjątek:
W rezultacie wyjątek może nie zostać obsłużony i albo rozprzestrzenić się zbyt wysoko w łańcuchu wywołań, aby można go było odpowiednio obsłużyć, lub spowodować bardzo nieprzydatne śledzenie użytkownika programu (co jest niestety zbyt częste w świecie python i java ).
Unikałbym również używania programów pobierających i ustawiających:
Zamiast właściwości i obiektów pobierających / ustawiających wolę wykonywać złożoną logikę w dobrze określonych miejscach, takich jak metoda sprawdzania poprawności:
lub podobna metoda Account.save.
Zauważ, że nie próbuję powiedzieć, że nie ma przypadków, w których właściwości są użyteczne, ale lepiej, jeśli uczynisz swoje zajęcia tak prostymi i przejrzystymi, że nie będziesz ich potrzebować.
źródło
validate()
metodę w klasie. Właściwość jest używana tylko wtedy, gdy za prostymobj.x = y
przypisaniem stoi złożona logika i zależy ona od logiki.Wydaje mi się, że właściwości polegają na tym, aby pozwolić ci na obciążenie pisarzami ustawiającymi i ustawiającymi tylko wtedy, gdy ich potrzebujesz.
Kultura programowania Java zdecydowanie zaleca, aby nigdy nie dawać dostępu do właściwości, a zamiast tego przechodzić przez moduły pobierające i ustawiające oraz tylko te, które są rzeczywiście potrzebne. Zawsze jest trochę gadatliwe, aby zawsze pisać te oczywiste fragmenty kodu i zauważyć, że w 70% przypadków nigdy nie są one zastępowane jakąś nietrywialną logiką.
W Pythonie ludzie faktycznie dbają o tego rodzaju koszty ogólne, abyś mógł zastosować następującą praktykę:
@property
aby je zaimplementować bez zmiany składni reszty kodu.źródło
Dziwi mnie, że nikt nie wspominał, że właściwości są metodami związanymi z klasą deskryptorów, Adam Donohue i NeilenMarais doszli do tego pomysłu w swoich postach - że funkcje pobierające i ustawiające są funkcjami i mogą być używane do:
Jest to inteligentny sposób na ukrycie szczegółów implementacji i cruft kodu, takich jak wyrażenia regularne, rzutowania typów, try .. oprócz bloków, asercji lub wartości obliczanych.
Ogólnie rzecz biorąc, wykonywanie CRUD na obiekcie może być często dość przyziemne, ale należy wziąć pod uwagę przykład danych, które zostaną utrwalone 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ć okropnymi, jeśli ... elif .. innymi drabinkami, które są tak brzydkie w kodzie OO - odsłaniając proste i elegancki
self.variable = something
i pomiń szczegóły dla programisty używającego ORM.Jeśli ktoś myśli o właściwościach tylko jako ponurym śladzie języka Bondage and Discipline (tj. Java), nie ma sensu w deskryptorach.
źródło
W złożonych projektach wolę używać właściwości tylko do odczytu (lub getterów) z jawną funkcją ustawiającą:
W długich projektach debugowanie i refaktoryzacja zajmuje więcej czasu niż napisanie samego kodu. Istnieje kilka wad używania,
@property.setter
które sprawiają, że debugowanie jest jeszcze trudniejsze:1) Python pozwala tworzyć nowe atrybuty dla istniejącego obiektu. To bardzo utrudnia śledzenie następującego błędu:
Jeśli twój obiekt jest skomplikowanym algorytmem, poświęcisz sporo czasu na sprawdzenie, dlaczego się nie zbiega (zauważ dodatkowe „t” w powyższej linii)
2) seter może czasem ewoluować do skomplikowanej i powolnej metody (np. Trafienie do bazy danych). Innemu deweloperowi byłoby bardzo trudno zrozumieć, dlaczego następująca funkcja jest bardzo wolna. Może spędzać dużo czasu na
do_something()
metodzie profilowania , podczas gdy wmy_object.my_attr = 4.
rzeczywistości jest przyczyną spowolnienia:źródło
Zarówno
@property
tradycyjny, jak i ustawiający mają swoje zalety. To zależy od twojego przypadku użycia.Zalety
@property
Nie musisz zmieniać interfejsu podczas zmiany implementacji dostępu do danych. Kiedy twój projekt jest mały, prawdopodobnie chcesz użyć bezpośredniego dostępu do atrybutu, aby uzyskać dostęp do członka klasy. Załóżmy na przykład, że masz obiekt
foo
typuFoo
, który ma element członkowskinum
. Następnie możesz po prostu zdobyć tego członkanum = foo.num
. W miarę rozwoju projektu możesz odczuwać potrzebę sprawdzenia lub debugowania prostego dostępu do atrybutów. Następnie można zrobić z@property
wewnątrz klasy. Interfejs dostępu do danych pozostaje taki sam, więc nie ma potrzeby modyfikowania kodu klienta.Cytowany z PEP-8 :
Używanie
@property
dostępu do danych w Pythonie jest uważane za Pythonic :Może wzmocnić twoją samoidentyfikację jako programista w języku Python (nie Java).
Może pomóc w rozmowie o pracę, jeśli ankieter uważa, że osoby pobierające i ustawiające w stylu Java są anty-wzorcami .
Zalety tradycyjnych pobierających i ustawiających
Tradycyjne metody pobierające i ustawiające umożliwiają bardziej skomplikowany dostęp do danych niż prosty dostęp do atrybutów. Na przykład, gdy 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 zwiększyć bezpośredni dostęp do elementu członkowskiego
foo.num = num
, możesz z łatwością rozszerzyć tradycyjnego setera za pomocą dodatkowegoforce
parametru:Tradycyjne metody pobierające i ustawiające wyraźnie pokazują, że dostęp członków klasy odbywa się za pomocą metody. To znaczy:
Wynik, który otrzymasz, może nie być taki sam, jak dokładnie przechowywany w tej klasie.
Nawet jeśli dostęp wygląda jak zwykły dostęp do atrybutu, wydajność może się znacznie od tego różnić.
O ile użytkownicy klasy nie oczekują, że
@property
kryją się za każdym oświadczeniem o dostępie do atrybutów, wyraźne określenie takich rzeczy może pomóc zminimalizować niespodzianki użytkowników klasy.Jak wspomniano w @NeilenMarais i w tym poście , rozszerzenie tradycyjnych metod pobierających i ustawiających w podklasach jest łatwiejsze niż rozszerzanie właściwości.
Tradycyjne pobierające i ustawiające są od dawna szeroko stosowane w różnych językach. Jeśli masz w zespole ludzi z różnych środowisk, wyglądają bardziej znajomo niż
@property
. Ponadto, w miarę rozwoju projektu, jeśli zajdzie potrzeba migracji z Pythona do innego języka, którego nie ma@property
, użycie tradycyjnych programów pobierających i ustawiających sprawiłoby, że migracja przebiegałaby płynniej.Ostrzeżenia
Ani
@property
tradycyjne metody pobierające i ustawiające nie czynią członka klasy prywatnym, nawet jeśli użyjesz podwójnego podkreślenia przed jego nazwą:źródło
Oto fragmenty „Skutecznego Pythona: 90 konkretnych sposobów na lepsze pisanie w Pythonie” (Niesamowita książka. Bardzo go polecam).
źródło