Próbuję zrozumieć, czym są deskryptory Pythona i do czego mogą być przydatne.
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 , jakproperty 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 i self, 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ściciela obj_instance, z descriptorobiektem:
obj_instance.descriptorwywołuje
descriptor.__get__(self, obj_instance, owner_class)zwracanie a value
W ten sposób działają wszystkie metody i getwłaściwość.
obj_instance.descriptor = valuewywołuje
descriptor.__set__(self, obj_instance, value)zwracanie None
Oto jak setterdziała właściwość.
del obj_instance.descriptorwywołuje
descriptor.__delete__(self, obj_instance)zwracanie None
Oto jak deleterdziała właściwość.
obj_instancejest instancją, której klasa zawiera instancję obiektu deskryptora. selfjest instancją deskryptora (prawdopodobnie tylko jedną dla klasy obj_instance)
Aby zdefiniować to za pomocą kodu, obiekt jest deskryptorem, jeśli zestaw jego atrybutów przecina się z dowolnym z wymaganych atrybutów:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
Danych deskryptorów ma __set__i / lub __delete__. Non-Data-deskryptorów ma ani ani .
__set____delete__
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Przykłady wbudowanych deskryptorów:
classmethod
staticmethod
property
- funkcje w ogólności
Deskryptory inne niż dane
Widzimy to classmethodi nie staticmethodjesteśmy deskryptorami danych:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
Oba mają tylko __get__metodę:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Zauważ, że wszystkie funkcje są również nie deskryptorami danych:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Deskryptor danych, property
Jest jednak propertydeskryptorem danych:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Kropkowane polecenie wyszukiwania
Są to ważne rozróżnienia , ponieważ wpływają na kolejność wyszukiwania dla wyszukiwania przerywanego.
obj_instance.attribute
- Najpierw powyższe sprawdza, czy atrybut jest deskryptorem danych w klasie instancji,
- Jeśli nie, to sprawdza, czy atrybut jest w
obj_instance„s __dict__, a następnie
- w końcu wraca do deskryptora nie-danych.
Konsekwencją 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:
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?
Twój deskryptor zapewnia, że zawsze masz zmiennoprzecinkowe atrybuty tej klasy Temperaturei nie możesz użyć deldo usunięcia tego atrybutu:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
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):
class Temperature(object):
celsius = 0.0
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:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- Co to jest tutaj instancja i właściciel? (w GET ). Jaki jest cel tych parametrów?
instancejest 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.
- Jak zadzwonić / skorzystać z tego przykładu?
Oto demonstracja:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Nie możesz usunąć atrybutu:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Nie można przypisać zmiennej, której nie można przekonwertować na zmiennoprzecinkową:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
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 propertydekoratora, który korzysta z tych samych deskryptorów pod maską, ale wprowadza zachowanie do implementacji klasy właściciela (ponownie, jak zdefiniowano powyżej):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
Który ma dokładnie takie samo oczekiwane zachowanie oryginalnego fragmentu kodu:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
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.
selfiinstance?