Sto cercando di capire quali sono i descrittori di Python e per cosa possono essere utili.
I descrittori sono attributi di classe (come proprietà o metodi) con uno dei seguenti metodi speciali:
__get__
(metodo descrittore non dati, ad esempio su un metodo / funzione)
__set__
(metodo descrittore di dati, ad esempio su un'istanza di proprietà)
__delete__
(metodo descrittore dati)
Questi oggetti descrittori possono essere utilizzati come attributi su altre definizioni di classi di oggetti. (Cioè, vivono nel__dict__
nell'oggetto della classe.)
Gli oggetti descrittori possono essere utilizzati per gestire a livello di programmazione i risultati di una ricerca punteggiata (ad es foo.descriptor
) In un'espressione normale, in un compito e persino in una cancellazione.
Funzioni / metodi, i metodi legati, property
, classmethod
, estaticmethod
tutto l'uso di questi metodi speciali per controllare come sono accessibili tramite la ricerca tratteggiata.
Un descrittore di dati , comeproperty
, può consentire una valutazione pigra degli attributi basata su uno stato più semplice dell'oggetto, consentendo alle istanze di usare meno memoria rispetto a se si pre-calcolasse ogni possibile attributo.
Un altro descrittore di dati, a member_descriptor
, creato da __slots__
, consente di risparmiare memoria consentendo alla classe di archiviare i dati in una struttura di dati mutevole simile a una tupla invece che più flessibile ma che richiede spazio __dict__
.
Descrittori non-dati, solitamente istanza, classe e metodi statici, ottenere i loro primi argomenti impliciti (solitamente denominati cls
e self
, rispettivamente) da loro metodo descrittore non di dati, __get__
.
La maggior parte degli utenti di Python deve imparare solo il semplice utilizzo e non ha bisogno di apprendere o comprendere ulteriormente l'implementazione dei descrittori.
Approfondimento: cosa sono i descrittori?
Un descrittore è un oggetto con uno dei seguenti metodi ( __get__
, __set__
o __delete__
), destinato ad essere utilizzato tramite tratteggiata ricerca come se fosse un tipico attributo di un'istanza. Per un oggetto proprietario obj_instance
, con un descriptor
oggetto:
obj_instance.descriptor
invoca il
descriptor.__get__(self, obj_instance, owner_class)
ritorno a value
Ecco come funzionano tutti i metodi e la get
proprietà on.
obj_instance.descriptor = value
invoca il
descriptor.__set__(self, obj_instance, value)
ritorno None
Ecco come funziona la setter
proprietà on.
del obj_instance.descriptor
invoca il
descriptor.__delete__(self, obj_instance)
ritorno None
Ecco come funziona la deleter
proprietà on.
obj_instance
è l'istanza la cui classe contiene l'istanza dell'oggetto descrittore. self
è l'istanza del descrittore (probabilmente solo uno per la classe diobj_instance
)
Per definirlo con il codice, un oggetto è un descrittore se l'insieme dei suoi attributi si interseca con uno degli attributi richiesti:
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))
Un descrittore di dati ha un __set__
e / o __delete__
.
Un descrittore non di dati non ha __set__
né __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))
Esempi di oggetti descrittori integrati:
classmethod
staticmethod
property
- funzioni in generale
Descrittori non dati
Possiamo vederlo classmethod
e staticmethod
sono descrittori non di dati:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
Entrambi hanno solo il __get__
metodo:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Si noti che tutte le funzioni sono anche descrittori non dati:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Descrittore dei dati, property
Tuttavia, property
è un descrittore di dati:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Ordine di ricerca punteggiato
Queste sono importanti distinzioni , in quanto influenzano l'ordine di ricerca per una ricerca punteggiata.
obj_instance.attribute
- Innanzi tutto, sembra che l'attributo sia un descrittore di dati sulla classe dell'istanza,
- In caso contrario, sembra vedere se l'attributo è in
obj_instance
's __dict__
, quindi
- infine ricade su un descrittore non di dati.
La conseguenza di questo ordine di ricerca è che i non descrittori di dati come funzioni / metodi possono essere sovrascritti dalle istanze .
Riepilogo e passaggi successivi
Abbiamo imparato che descrittori sono oggetti con qualsiasi __get__
, __set__
o __delete__
. Questi oggetti descrittori possono essere utilizzati come attributi su altre definizioni di classi di oggetti. Ora vedremo come vengono utilizzati, usando il tuo codice come esempio.
Analisi del codice dalla domanda
Ecco il tuo codice, seguito dalle tue domande e risposte a ciascuno:
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()
- Perché ho bisogno della classe descrittore?
Il tuo descrittore ti assicura di avere sempre un float per questo attributo di classe di Temperature
e che non puoi usare del
per cancellare l'attributo:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Altrimenti, i descrittori ignorano la classe proprietario e le istanze del proprietario, invece, memorizzando lo stato nel descrittore. Puoi condividere lo stato con tutte le istanze con la stessa facilità con un semplice attributo di classe (purché lo imposti sempre come float sulla classe e non lo elimini mai, oppure ti senti a tuo agio con gli utenti del tuo codice):
class Temperature(object):
celsius = 0.0
Questo ti dà esattamente lo stesso comportamento del tuo esempio (vedi la risposta alla domanda 3 di seguito), ma usa un Pythons builtin ( property
) e sarebbe considerato più idiomatico:
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)
- Che cos'è l'istanza e il proprietario qui? (in get ). Qual è lo scopo di questi parametri?
instance
è l'istanza del proprietario che chiama il descrittore. Il proprietario è la classe in cui viene usato l'oggetto descrittore per gestire l'accesso al punto dati. Vedi le descrizioni dei metodi speciali che definiscono i descrittori accanto al primo paragrafo di questa risposta per nomi di variabili più descrittivi.
- Come chiamerei / utilizzerei questo esempio?
Ecco una dimostrazione:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Non è possibile eliminare l'attributo:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
E non puoi assegnare una variabile che non può essere convertita in float:
>>> 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
Altrimenti, quello che hai qui è uno stato globale per tutte le istanze, che viene gestito assegnando a qualsiasi istanza.
Il modo previsto in cui i programmatori Python più esperti otterrebbero questo risultato sarebbe di usare il property
decoratore, che utilizza gli stessi descrittori sotto il cofano, ma introduce il comportamento nell'implementazione della classe del proprietario (di nuovo, come definito sopra):
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)
Che ha esattamente lo stesso comportamento previsto del codice originale:
>>> 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
Conclusione
Abbiamo coperto gli attributi che definiscono i descrittori, la differenza tra descrittori di dati e non, oggetti incorporati che li usano e domande specifiche sull'uso.
Quindi, come useresti l'esempio della domanda? Spero che non lo faresti. Spero che inizierai con il mio primo suggerimento (un semplice attributo di classe) e passerai al secondo suggerimento (il decoratore di proprietà) se ritieni che sia necessario.
self
einstance
?