Ja próbuje zrozumieć, co deskryptory Pythona są i jakie są przydatne do. Rozumiem, jak działają, ale oto moje wątpliwości. Rozważ następujący kod:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
Dlaczego potrzebuję klasy deskryptora?
Co jest
instance
iowner
tutaj? (w__get__
). Jaki jest cel tych parametrów?Jak zadzwonić / skorzystać z tego przykładu?
źródło
self
iinstance
?self
jest instancją deskryptora,instance
jest instancją klasy (jeśli jest utworzona instancja), w której znajduje się deskryptor (instance.__class__ is owner
).Temperature.celsius
podaje wartość0.0
zgodnie z kodemcelsius = Celsius()
. Wywoływany jest deskryptor Celsjusz, więc jego instancja ma wartość początkową0.0
przypisaną do atrybutu Klasa temperatury, Celsjusza.Daje to dodatkową kontrolę nad działaniem atrybutów. Jeśli na przykład przyzwyczaiłeś się do programów pobierających i ustawiających w Javie, jest to sposób Pythona. Jedną z zalet jest to, że wygląda na użytkowników jak atrybut (nie ma zmiany w składni). Możesz więc zacząć od zwykłego atrybutu, a następnie, gdy musisz zrobić coś wymyślnego, przełącz się na deskryptor.
Atrybut to tylko zmienna wartość. Deskryptor umożliwia wykonanie dowolnego kodu podczas odczytu lub ustawiania (lub usuwania) wartości. Można więc sobie wyobrazić użycie go do mapowania atrybutu na pole w bazie danych, na przykład - rodzaj ORM.
Innym zastosowaniem może być odmowa zaakceptowania nowej wartości poprzez zgłoszenie wyjątku
__set__
- efektywnie czyniąc „atrybut” tylko do odczytu.Jest to dość subtelne (i powód, dla którego piszę tutaj nową odpowiedź - znalazłem to pytanie, zastanawiając się nad tym samym i nie znalazłem istniejącej odpowiedzi tak wspaniale).
Deskryptor jest zdefiniowany w klasie, ale zwykle jest wywoływany z instancji. Kiedy to się nazywa z instancji zarówno
instance
iowner
są ustawione (i można pracowaćowner
zinstance
tak wydaje się trochę bez sensu). Ale kiedy jest wywoływany z klasy,owner
ustawiany jest tylko - i dlatego tam jest.Jest to potrzebne tylko
__get__
dlatego, że jako jedyny można wywołać klasę. Jeśli ustawisz wartość klasy, sam deskryptor. Podobnie do usunięcia. Dlategoowner
nie jest tam potrzebny.Oto fajna sztuczka wykorzystująca podobne klasy:
(Używam Pythona 3; dla Pythona 2 musisz upewnić się, że te podziały to
/ 5.0
i/ 9.0
). To daje:Teraz istnieją inne, prawdopodobnie lepsze sposoby osiągnięcia tego samego efektu w pythonie (np. Gdyby celsius był właściwością, która jest tym samym podstawowym mechanizmem, ale umieszcza całe źródło w klasie temperaturowej), ale pokazuje to, co można zrobić ...
źródło
Deskryptory są atrybutami klas (takimi jak właściwości lub metody) za pomocą dowolnej z następujących metod specjalnych:
__get__
(metoda deskryptora innego niż dane, na przykład metoda / funkcja)__set__
(metoda deskryptora danych, na przykład w instancji właściwości)__delete__
(metoda deskryptora danych)Te obiekty deskryptorów mogą być używane jako atrybuty w innych definicjach klas obiektów. (To znaczy, mieszkają w
__dict__
obiekcie klasy).Obiektów deskryptorów można używać do programowego zarządzania wynikami wyszukiwania z kropkami (np
foo.descriptor
) W normalnym wyrażeniu, przypisaniu, a nawet usunięciu.Funkcje / metody, metody powiązane,
property
,classmethod
, istaticmethod
wszystkie te szczególne zastosowanie metody do kontroli, jak są one dostępne za pośrednictwem przerywanej odnośnika.ZA Deskryptor danych , jak
property
można pozwolić na leniwe oceny atrybutów w oparciu o prostszej stanu obiektu, dzięki czemu instancje użyć mniej pamięci niż gdybyś precomputed każdy możliwy atrybut.Kolejny deskryptor danych, a
member_descriptor
, utworzony przez__slots__
, pozwala na oszczędność pamięci, umożliwiając klasie przechowywanie danych w zmiennej strukturze krotkowej podobnej do krotki zamiast bardziej elastycznej, ale zajmującej dużo miejsca__dict__
.Deskryptory niebędące danymi, zwykle instancja, klasa i metody statyczne, otrzymują swoje niejawne pierwsze argumenty (zwykle nazywane
cls
iself
, odpowiednio) z ich non-danych metody deskryptora,__get__
.Większość użytkowników Pythona musi nauczyć się jedynie prostego użytkowania i nie musi dalej uczyć się ani rozumieć implementacji deskryptorów.
W głębi: czym są deskryptory?
Deskryptor to obiekt posiadający dowolną z następujących metod (
__get__
,__set__
lub__delete__
), przeznaczony do użycia za pomocą wyszukiwania przerywanego, jakby był typowym atrybutem instancji. W przypadku obiektu właścicielaobj_instance
, zdescriptor
obiektem:obj_instance.descriptor
wywołujedescriptor.__get__(self, obj_instance, owner_class)
zwracanie avalue
W ten sposób działają wszystkie metody i
get
właściwość.obj_instance.descriptor = value
wywołujedescriptor.__set__(self, obj_instance, value)
zwracanieNone
Oto jak
setter
działa właściwość.del obj_instance.descriptor
wywołujedescriptor.__delete__(self, obj_instance)
zwracanieNone
Oto jak
deleter
działa właściwość.obj_instance
jest instancją, której klasa zawiera instancję obiektu deskryptora.self
jest instancją deskryptora (prawdopodobnie tylko jedną dla klasyobj_instance
)Aby zdefiniować to za pomocą kodu, obiekt jest deskryptorem, jeśli zestaw jego atrybutów przecina się z dowolnym z wymaganych atrybutów:
Danych deskryptorów ma
__set__
i / lub__delete__
. Non-Data-deskryptorów ma ani ani .__set__
__delete__
Przykłady wbudowanych deskryptorów:
classmethod
staticmethod
property
Deskryptory inne niż dane
Widzimy to
classmethod
i niestaticmethod
jesteśmy deskryptorami danych:Oba mają tylko
__get__
metodę:Zauważ, że wszystkie funkcje są również nie deskryptorami danych:
Deskryptor danych,
property
Jest jednak
property
deskryptorem danych:Kropkowane polecenie wyszukiwania
Są to ważne rozróżnienia , ponieważ wpływają na kolejność wyszukiwania dla wyszukiwania przerywanego.
obj_instance
„s__dict__
, a następnieKonsekwencją tej kolejności wyszukiwania jest to, że nie-deskryptory danych, takie jak funkcje / metody, mogą zostać zastąpione przez instancje .
Podsumowanie i kolejne kroki
Dowiedzieliśmy się, że deskryptory są obiektami z dowolnego
__get__
,__set__
lub__delete__
. Te obiekty deskryptorów mogą być używane jako atrybuty w innych definicjach klas obiektów. Teraz przyjrzymy się, jak są one używane, wykorzystując Twój kod jako przykład.Analiza kodu z pytania
Oto Twój kod, a następnie pytania i odpowiedzi na każde z nich:
Twój deskryptor zapewnia, że zawsze masz zmiennoprzecinkowe atrybuty tej klasy
Temperature
i nie możesz użyćdel
do usunięcia tego atrybutu:W przeciwnym razie deskryptory zignorują klasę właściciela i instancje właściciela, zapisując stan w deskryptorze. Równie łatwo możesz współdzielić stan we wszystkich instancjach za pomocą prostego atrybutu klasy (o ile zawsze ustawiasz go jako zmiennoprzecinkowy dla klasy i nigdy go nie usuwasz, lub czujesz się swobodnie z użytkownikami twojego kodu):
To zapewnia dokładnie takie samo zachowanie jak w twoim przykładzie (patrz odpowiedź na pytanie 3 poniżej), ale używa wbudowanej wersji Pythona (
property
) i byłoby uważane za bardziej idiomatyczne:instance
jest instancją właściciela, który wywołuje deskryptor. Właściciel jest klasą, w której obiekt deskryptora służy do zarządzania dostępem do punktu danych. Zobacz opisy specjalnych metod definiujących deskryptory obok pierwszego akapitu tej odpowiedzi, aby uzyskać bardziej opisowe nazwy zmiennych.Oto demonstracja:
Nie możesz usunąć atrybutu:
Nie można przypisać zmiennej, której nie można przekonwertować na zmiennoprzecinkową:
W przeciwnym razie masz tutaj stan globalny dla wszystkich instancji, którym zarządza się poprzez przypisanie do dowolnej instancji.
Oczekiwanym sposobem, w jaki najbardziej doświadczeni programiści Pythona osiągnęliby ten wynik, byłoby użycie
property
dekoratora, który korzysta z tych samych deskryptorów pod maską, ale wprowadza zachowanie do implementacji klasy właściciela (ponownie, jak zdefiniowano powyżej):Który ma dokładnie takie samo oczekiwane zachowanie oryginalnego fragmentu kodu:
Wniosek
Omówiliśmy atrybuty definiujące deskryptory, różnicę między deskryptorami danych i nie-danych, wbudowane obiekty, które ich używają, oraz szczegółowe pytania dotyczące użycia.
Więc ponownie, jak użyłbyś przykładu pytania? Mam nadzieję, że nie. Mam nadzieję, że zaczniesz od mojej pierwszej sugestii (prosty atrybut klasy) i przejdziesz do drugiej sugestii (dekoratora nieruchomości), jeśli uważasz, że jest to konieczne.
źródło
Zanim przejdziemy do szczegółów deskryptorów, może być ważne, aby wiedzieć, jak działa wyszukiwanie atrybutów w Pythonie. Zakłada się, że klasa nie ma metaklasy i że używa domyślnej implementacji
__getattribute__
(obie można wykorzystać do „dostosowania” zachowania).Najlepszą ilustracją wyszukiwania atrybutów (w Pythonie 3.x lub klas nowego stylu w Pythonie 2.x) jest w tym przypadku Zrozumienie metaklas Pythona (dziennik kodów Jonela) . Obraz używa
:
jako substytutu dla „niestandardowego wyszukiwania atrybutów”.Stanowi odnośnika atrybutu
foobar
na zasadzieinstance
zClass
:Ważne są tutaj dwa warunki:
instance
ma wpis dla nazwy atrybutu i ma__get__
i__set__
.instance
nie ma pozycji dla nazwy atrybutu, ale klasa ją ma i ma__get__
.Właśnie tam wchodzą deskryptory:
__get__
i__set__
.__get__
.W obu przypadkach zwracana wartość jest
__get__
wywoływana z instancją jako pierwszym argumentem, a klasą jako drugim argumentem.Wyszukiwanie jest jeszcze bardziej skomplikowane w przypadku wyszukiwania atrybutów klasy (patrz na przykład wyszukiwanie atrybutów klasy (na wyżej wspomnianym blogu) ).
Przejdźmy do konkretnych pytań:
W większości przypadków nie musisz pisać klas deskryptorów! Jednak prawdopodobnie jesteś bardzo regularnym użytkownikiem końcowym. Na przykład funkcje. Funkcje są deskryptorami, dlatego funkcji można używać jako metod z
self
domyślnie przekazywanym jako pierwszy argument.Jeśli spojrzysz
test_method
na instancję, otrzymasz „powiązaną metodę”:Podobnie można również powiązać funkcję,
__get__
ręcznie wywołując jej metodę (niezbyt zalecane, tylko w celach ilustracyjnych):Możesz nawet nazwać tę „samodzielną metodą”:
Zauważ, że nie podałem żadnych argumentów, a funkcja zwróciła instancję, którą związałem!
Funkcje to deskryptory inne niż dane !
Oto niektóre wbudowane przykłady deskryptora danych
property
. Zaniedbaniegetter
,setter
i deskryptor jest (z deskryptora HowTo Guide "Properties" ):deleter
property
Ponieważ jest to dane deskryptor to jest wywoływana, gdy patrzysz w górę „” w imię
property
i po prostu deleguje do funkcji ozdobione@property
,@name.setter
oraz@name.deleter
(jeśli występuje).Istnieje kilka innych deskryptorów w bibliotece standardowej, na przykład
staticmethod
,classmethod
.Deskryptory są łatwe (choć rzadko ich potrzebujesz): abstrakcyjny wspólny kod dostępu do atrybutów.
property
jest abstrakcją na przykład dostępu do zmiennej,function
zapewnia abstrakcję dla metod,staticmethod
zapewnia abstrakcję dla metod, które nie wymagają dostępu do instancji iclassmethod
zapewnia abstrakcję dla metod wymagających dostępu do klasy, a nie dostępu do instancji (jest to nieco uproszczone).Innym przykładem może być właściwość klasy .
Ciekawym przykładem (korzystanie
__set_name__
z Pythona 3.6) może być również właściwość, która zezwala tylko na określony typ:Następnie możesz użyć deskryptora w klasie:
I baw się z tym trochę:
Lub „leniwa nieruchomość”:
Są to przypadki, w których przeniesienie logiki do wspólnego deskryptora może mieć sens, jednak można je również rozwiązać (ale być może poprzez powtórzenie kodu) innymi środkami.
To zależy od sposobu wyszukiwania atrybutu. Jeśli szukasz atrybutu na instancji, to:
W przypadku wyszukiwania atrybutu w klasie (przy założeniu, że deskryptor jest zdefiniowany w klasie):
None
Więc w zasadzie trzeci argument jest konieczne, jeśli chcesz, aby dostosować zachowanie podczas wykonywania look-up na poziomie klasy (bo
instance
jestNone
).Twój przykład jest w zasadzie właściwością, która pozwala tylko na wartości, które można przekonwertować
float
i które są współużytkowane przez wszystkie instancje klasy (i klasy - chociaż można użyć tylko dostępu do odczytu w klasie, w przeciwnym razie zastąpiłbyś instancję deskryptora ):Właśnie dlatego deskryptory zazwyczaj używają drugiego argumentu (
instance
) do przechowywania wartości, aby uniknąć jej udostępniania. Jednak w niektórych przypadkach dzielenie wartości między instancjami może być pożądane (chociaż w tej chwili nie mogę wymyślić scenariusza). Jednak nie ma praktycznie żadnego sensu dla własności Celsjusza w klasie temperaturowej ... może z wyjątkiem czysto akademickich ćwiczeń.źródło
Zainspirowany przez Fluent Python przez Buciano Ramalho
Obrazowanie masz taką klasę
Powinniśmy sprawdzić wagę i cenę, aby uniknąć przypisania im liczby ujemnej, możemy napisać mniej kodu, jeśli użyjemy deskryptora jako proxy, ponieważ
następnie zdefiniuj klasę LineItem w następujący sposób:
i możemy rozszerzyć klasę Quantity, aby przeprowadzić bardziej powszechne sprawdzanie poprawności
źródło
weight = Quantity()
Ale wartości muszą być ustawione w przestrzeni nazw instancji tylko przy użyciuself
(np.self.weight = 4
), W przeciwnym razie atrybut zostałby powiązany z nową wartością a deskryptor zostałby odrzucony. Fajnie!weight = Quantity()
jako klasy zmiennej i jej__get__
i__set__
pracują na zmiennej instancji. W jaki sposób?Próbowałem (z niewielkimi zmianami, jak sugerowano) kod z odpowiedzi Andrew Cooke. (Używam Pythona 2.7).
Kod:
Wynik:
W przypadku Pythona wcześniejszego niż 3 upewnij się, że podklasujesz z obiektu, co sprawi, że deskryptor będzie działał poprawnie, ponieważ magia get nie działa dla klas starego stylu.
źródło
Zobaczysz https://docs.python.org/3/howto/descriptor.html#properties
źródło