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 descriptor
obiektem:
obj_instance.descriptor
wywołuje
descriptor.__get__(self, obj_instance, owner_class)
zwracanie a value
W ten sposób działają wszystkie metody i get
właściwość.
obj_instance.descriptor = value
wywołuje
descriptor.__set__(self, obj_instance, value)
zwracanie None
Oto jak setter
działa właściwość.
del obj_instance.descriptor
wywołuje
descriptor.__delete__(self, obj_instance)
zwracanie None
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 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 classmethod
i nie staticmethod
jesteś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 property
deskryptorem 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 Temperature
i nie możesz użyć del
do 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?
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.
- 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 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):
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.
self
iinstance
?