Comprensione dei descrittori __get__ e __set__ e Python


310

Sto cercando di capire quali sono i descrittori di Python e per cosa sono utili. Capisco come funzionano, ma qui ci sono i miei dubbi. Considera il seguente codice:

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()
  1. Perché ho bisogno della classe descrittore?

  2. Che cos'è instancee ownerqui? (in __get__). Qual è lo scopo di questi parametri?

  3. Come chiamerei / utilizzerei questo esempio?

Risposte:


147

Il descrittore è come propertyviene implementato il tipo di Python . Un descrittore semplicemente attrezzi __get__, __set__ecc e viene quindi aggiunta ad un'altra classe nella sua definizione (come avete fatto in precedenza con la classe di temperatura). Per esempio:

temp=Temperature()
temp.celsius #calls celsius.__get__

L'accesso alla proprietà a cui è stato assegnato il descrittore ( celsiusnell'esempio precedente) chiama il metodo descrittore appropriato.

instancein __get__è l'istanza della classe (così sopra, __get__riceverebbe temp, mentre ownerè la classe con il descrittore (così sarebbe Temperature).

È necessario utilizzare una classe descrittore per incapsulare la logica che la alimenta. In questo modo, se il descrittore viene utilizzato per memorizzare nella cache alcune operazioni costose (ad esempio), potrebbe memorizzare il valore su se stesso e non sulla sua classe.

Un articolo sui descrittori è disponibile qui .

EDIT: Come ha sottolineato Jchl nei commenti, se ci provi semplicemente Temperature.celsius, instancelo sarà None.


6
Qual è la differenza tra selfe instance?
Lemma Prism,

2
'instance' può essere un'istanza di qualsiasi classe, self sarà un'istanza della stessa classe.
TheBeginner

3
@LemmaPrism selfè l'istanza del descrittore, instanceè l'istanza della classe (se istanziata) in cui si trova il descrittore ( instance.__class__ is owner).
Fino al

Temperature.celsiusdà il valore in 0.0base al codice celsius = Celsius(). Il descrittore Celsius viene chiamato, quindi la sua istanza ha il valore init 0.0assegnato all'attributo della classe Temperature, celsius.
Angel Salazar

109

Perché ho bisogno della classe descrittore?

Ti dà un controllo extra su come funzionano gli attributi. Se ad esempio sei abituato a getter e setter in Java, allora è il modo di farlo di Python. Un vantaggio è che assomiglia agli utenti proprio come un attributo (non ci sono cambiamenti nella sintassi). Quindi puoi iniziare con un normale attributo e poi, quando devi fare qualcosa di stravagante, passare a un descrittore.

Un attributo è solo un valore mutabile. Un descrittore consente di eseguire codice arbitrario durante la lettura o l'impostazione (o l'eliminazione) di un valore. Quindi potresti immaginare di usarlo per mappare un attributo a un campo in un database, ad esempio una specie di ORM.

Un altro uso potrebbe essere il rifiuto di accettare un nuovo valore generando un'eccezione __set__, rendendo effettivamente "l'attributo" di sola lettura.

Che cos'è instancee ownerqui? (in __get__). Qual è lo scopo di questi parametri?

Questo è piuttosto sottile (e il motivo per cui sto scrivendo una nuova risposta qui - ho trovato questa domanda mentre mi chiedevo la stessa cosa e non ho trovato la risposta esistente così grande).

Un descrittore è definito su una classe, ma in genere viene chiamato da un'istanza. Quando si chiama da un'istanza sia instancee ownersono impostati (e si può lavorare fuori ownerda instancecosì sembra un pò inutile). Ma quando viene chiamato da una classe, ownerviene impostato solo - motivo per cui è lì.

Questo è necessario solo __get__perché è l'unico che può essere chiamato in una classe. Se si imposta il valore della classe, si imposta il descrittore stesso. Allo stesso modo per la cancellazione. Ecco perché ownernon è necessario lì.

Come chiamerei / utilizzerei questo esempio?

Bene, ecco un bel trucco usando classi simili:

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5


class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f


t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(Sto usando Python 3; per Python 2 devi assicurarti che quelle divisioni siano / 5.0e / 9.0). Questo dà:

100.0
32.0

Ora ci sono altri modi, probabilmente migliori, per ottenere lo stesso effetto in Python (ad esempio se Celsius fosse una proprietà, che è lo stesso meccanismo di base ma colloca tutta la sorgente all'interno della classe Temperature), ma che mostra cosa si può fare ...


2
Le conversioni sono errate: dovrebbero essere C = 5 (F − 32) / 9, F = 32 + 9C / 5.
musiphil,

1
Assicurati di avere un oggetto Temperatura. Fare quanto segue rovina tutto. t1 = Temperatura (190) stampa t1.celsius t1.celsius = 100 stampa t1.fahrenheit Ora quando controlli t.celcius e t.fahrenheit, anche loro vengono modificati. t.celcius è 115 e t.fahrenheit è 32. che è chiaramente sbagliato. @Eric
Ishan Bhatt

1
@IshanBhatt: penso che sia a causa dell'errore segnalato dal musiphil sopra. Inoltre, questa non è la mia risposta
Eric

69

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 clse 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 descriptoroggetto:

  • obj_instance.descriptorinvoca il
    descriptor.__get__(self, obj_instance, owner_class)ritorno a value
    Ecco come funzionano tutti i metodi e la getproprietà on.

  • obj_instance.descriptor = valueinvoca il
    descriptor.__set__(self, obj_instance, value)ritorno None
    Ecco come funziona la setterproprietà on.

  • del obj_instance.descriptorinvoca il
    descriptor.__delete__(self, obj_instance)ritorno None
    Ecco come funziona la deleterproprietà 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____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 classmethode staticmethodsono 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
  1. Innanzi tutto, sembra che l'attributo sia un descrittore di dati sulla classe dell'istanza,
  2. In caso contrario, sembra vedere se l'attributo è in obj_instance's __dict__, quindi
  3. 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()
  1. Perché ho bisogno della classe descrittore?

Il tuo descrittore ti assicura di avere sempre un float per questo attributo di classe di Temperaturee che non puoi usare delper 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)
  1. 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.

  1. 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 propertydecoratore, 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.


1
Bene, ho imparato di più da questa risposta (sicuramente ho imparato anche dagli altri). Una domanda su questa affermazione "Il modo in cui i programmatori Python più esperti avrebbero raggiunto questo risultato ...". La classe Temeperature definita prima e dopo l'istruzione è identica. Mi sono perso quello che stai arrivando qui?
Yolo Voe,

1
@YoloVoe no, è vero, ho aggiunto alcune parole tra parentesi per sottolineare che è una ripetizione di quanto sopra.
Aaron Hall

1
Questa è una risposta INCREDIBILE. Dovrò leggerlo più volte, ma mi sento come se la mia comprensione di Python fosse appena salita di qualche gradino
Lucas Young,

20

Prima di entrare nei dettagli dei descrittori, potrebbe essere importante sapere come funziona la ricerca degli attributi in Python. Ciò presuppone che la classe non abbia metaclasse e che utilizzi l'implementazione predefinita di__getattribute__ (entrambi possono essere usati per "personalizzare" il comportamento).

La migliore illustrazione della ricerca degli attributi (in Python 3.xo per le classi di nuovo stile in Python 2.x) in questo caso è da Capire le metaclasse di Python (il codice di ionel) . L'immagine usa: come sostituto della "ricerca di attributi non personalizzabile".

Questo rappresenta la ricerca di un attributo foobarsu uno instancedi Class:

inserisci qui la descrizione dell'immagine

Due condizioni sono importanti qui:

  • Se la classe di instanceha una voce per il nome dell'attributo e ha __get__e __set__.
  • Se il noninstance ha una voce per il nome dell'attributo ma la classe ne ha una e ha __get__.

Ecco dove entrano in gioco i descrittori:

  • Descrittori di dati che hanno entrambi __get__e __set__.
  • Descrittori non di dati che hanno solo __get__.

In entrambi i casi il valore restituito viene __get__chiamato con l'istanza come primo argomento e la classe come secondo argomento.

La ricerca è ancora più complicata per la ricerca degli attributi di classe (vedere ad esempio la ricerca degli attributi di classe (nel blog sopra citato) ).

Passiamo alle tue domande specifiche:

Perché ho bisogno della classe descrittore?

Nella maggior parte dei casi non è necessario scrivere classi descrittive! Tuttavia, probabilmente sei un utente finale molto regolare. Ad esempio funzioni. Le funzioni sono descrittori, ecco come le funzioni possono essere usate come metodi con selfimplicitamente passato come primo argomento.

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

Se cerchi test_methodin un'istanza otterrai un "metodo associato":

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

Allo stesso modo potresti anche associare una funzione invocando il suo __get__metodo manualmente (non proprio raccomandato, solo a scopo illustrativo):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

Puoi persino chiamare questo "metodo auto-associato":

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

Nota che non ho fornito alcun argomento e la funzione ha restituito l'istanza che avevo associato!

Le funzioni sono descrittori non dati !

Alcuni esempi integrati di un descrittore di dati sarebbero property. Trascurando getter, settere deleteril propertydescrittore è (dal Manuale del descrittore "Proprietà" ):

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

Dal momento che è un descrittore di dati viene invocato ogni volta che si guarda il "nome" della propertyIT e semplicemente delegati alle funzioni decorati con @property, @name.settere@name.deleter (se presente).

Ci sono molti altri descrittori nella libreria standard, ad esempio staticmethod,classmethod .

Il punto dei descrittori è semplice (anche se raramente ne hai bisogno): Codice comune astratto per l'accesso agli attributi. propertyè un'astrazione per l'accesso variabile ad esempio, functionfornisce un'astrazione per metodi, staticmethodfornisce un'astrazione per metodi che non richiedono l'accesso all'istanza eclassmethod fornisce un'astrazione per i metodi che richiedono l'accesso alla classe anziché l'accesso all'istanza (questo è un po 'semplificato).

Un altro esempio potrebbe essere una proprietà di classe .

Un esempio divertente (utilizzando __set_name__da Python 3.6) potrebbe anche essere una proprietà che consente solo un tipo specifico:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

Quindi è possibile utilizzare il descrittore in una classe:

class Test(object):
    int_prop = TypedProperty(int)

E giocando un po 'con esso:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

O una "proprietà pigra":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

Questi sono casi in cui potrebbe avere senso spostare la logica in un descrittore comune, tuttavia si potrebbero anche risolverli (ma forse ripetendo del codice) con altri mezzi.

Che cos'è instancee ownerqui? (in __get__). Qual è lo scopo di questi parametri?

Dipende da come si cerca l'attributo. Se cerchi l'attributo in un'istanza, allora:

  • il secondo argomento è l'istanza in cui si cerca l'attributo
  • il terzo argomento è la classe dell'istanza

Nel caso in cui cerchi l'attributo sulla classe (supponendo che il descrittore sia definito sulla classe):

  • il secondo argomento è None
  • il terzo argomento è la classe in cui si cerca l'attributo

Quindi, in sostanza, il terzo argomento è necessario se si desidera personalizzare il comportamento quando si esegue una ricerca a livello di classe (perché lo instanceè None).

Come chiamerei / utilizzerei questo esempio?

Il tuo esempio è fondamentalmente una proprietà che consente solo i valori che possono essere convertiti in floate che è condivisa tra tutte le istanze della classe (e sulla classe - sebbene si possa usare solo l'accesso "read" sulla classe altrimenti sostituire l'istanza del descrittore ):

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

Ecco perché i descrittori generalmente usano il secondo argomento ( instance) per memorizzare il valore per evitare di condividerlo. Tuttavia, in alcuni casi potrebbe essere desiderato condividere un valore tra istanze (anche se al momento non riesco a pensare a uno scenario). Tuttavia non ha praticamente senso per una proprietà Celsius in una classe di temperatura ... tranne forse come esercizio puramente accademico.


non sono sicuro se lo sfondo trasparente della grafica che soffre davvero in modalità oscura debba essere segnalato come bug a StackOverflow.
Tshirtman,

@Tshirtman Penso che questo sia un problema con l'immagine stessa. Non è completamente trasparente ... L'ho preso dal post del blog e non so come ricrearlo con uno sfondo trasparente adeguato. È un
peccato

9

Perché ho bisogno della classe descrittore?

Ispirato da Fluent Python di Buciano Ramalho

Imaging hai una lezione come questa

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

Dovremmo convalidare il peso e il prezzo per evitare di assegnare loro un numero negativo, possiamo scrivere meno codice se usiamo il descrittore come proxy come questo

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

quindi definire la classe LineItem in questo modo:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

e possiamo estendere la classe Quantity per eseguire una convalida più comune


1
Caso d'uso interessante, in quanto mostra come utilizzare il descrittore per interagire con più istanze di utenti. Inizialmente non ho capito il punto importante: un attributo con un descrittore deve essere creato nello spazio dei nomi della classe (ad es. weight = Quantity(), Ma i valori devono essere impostati nello spazio dei nomi dell'istanza solo usando self(ad es. self.weight = 4), Altrimenti l'attributo verrebbe rebound al nuovo valore e il descrittore verrebbe scartato. Bello!
Min.

Non sono in grado di capire una cosa. Stai definendo weight = Quantity()come variabile di classe e la sua __get__e __set__stai lavorando sulla variabile di istanza. Come?
Technocrat,

0

Ho provato (con piccole modifiche come suggerito) il codice dalla risposta di Andrew Cooke. (Sto eseguendo Python 2.7).

Il codice:

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

Il risultato:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

Con Python precedente al 3, assicurati di eseguire la sottoclasse dall'oggetto che farà funzionare correttamente il descrittore poiché la magia get non funziona per le classi di vecchio stile.


1
I descrittori funzionano solo con nuove classi di stile. Per python 2.x questo significa derivare la tua classe da "oggetto", che è di default in Python 3.
Ivo van der Wijk,

0

Vedresti https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

1
Questo non risponde alla domanda o fornisce informazioni utili.
Sebastian Nielsen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.