Esempio reale su come utilizzare la funzione di proprietà in Python?


142

Sono interessato a come utilizzare @propertyin Python. Ho letto i documenti di Python e l'esempio lì, secondo me, è solo un codice giocattolo:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Non so quali vantaggi posso ottenere avvolgendo il _xriempito con il decoratore di proprietà. Perché non implementare solo come:

class C(object):
    def __init__(self):
        self.x = None

Penso che la funzione proprietà potrebbe essere utile in alcune situazioni. Ma quando? Qualcuno potrebbe darmi alcuni esempi del mondo reale?

Grazie.


9
Questa è la spiegazione migliore e più pulita che ho trovato sul decoratore di proprietà [clicca qui ]
Sumudu

2
@Anubis nell'ultimo esempio nel collegamento fornito, l'impostazione c = Celsius (-500) non ha generato alcun ValueError, che penso non stia raggiungendo il risultato desiderato.
Sajuuk,

Concordo con @Anubi. È implementato correttamente qui: python-course.eu/python3_properties.php
anon01

Risposte:


91

Altri esempi potrebbero essere la convalida / il filtraggio degli attributi impostati (costringendoli a essere limitati o accettabili) e una valutazione pigra di termini complessi o in rapido cambiamento.

Calcolo complesso nascosto dietro un attributo:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

convalida:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

1
Mi piace l'esempio di PDB_Calculator: le cose complicate vengono sottratte, tutto funziona e l'utente può godere della semplicità!
Adam Kurkiewicz,

2
probabilmente, dal punto di vista dei professionisti, questi sono ottimi esempi. Ma, come un noobie, trovo questi esempi abbastanza inefficaci. mio cattivo ... :(
kmonsoor

77

Un semplice caso d'uso sarà quello di impostare un attributo di istanza di sola lettura, poiché sai che portare un nome di variabile con un carattere di sottolineatura _xin Python di solito significa che è privato (uso interno) ma a volte vogliamo essere in grado di leggere l'attributo di istanza e non di scrivere così possiamo usarlo propertyper questo:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

6
Si può ancora impostare c._x, se l'utente lo desidera. Python in realtà non ha veri attributi privati.

20

Dai un'occhiata a questo articolo per un uso molto pratico. In breve, spiega come in Python di solito si possa abbandonare il metodo getter / setter esplicito, poiché se si ha bisogno di loro ad un certo punto è possibile utilizzare propertyper un'implementazione senza soluzione di continuità.


15

Una cosa per cui l'ho usato è la memorizzazione nella cache di valori lenti da cercare, ma immutabili, memorizzati in un database. Questo generalizza a qualsiasi situazione in cui i tuoi attributi richiedono il calcolo o qualche altra lunga operazione (es. Controllo del database, comunicazione di rete) che vuoi fare solo su richiesta.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Questo si trovava in un'app Web in cui una determinata visualizzazione di pagina poteva richiedere solo un particolare attributo di questo tipo, ma gli oggetti sottostanti stessi potevano avere diversi di questi attributi: inizializzarli tutti sulla costruzione sarebbe dispendioso e le proprietà mi permettevano di essere flessibile in cui gli attributi sono pigri e quali no.


1
Non puoi usare @cached_propertyper questo?
Adarsh

@adarsh ​​- Sembra interessante. Dov'è?
Detly

L'ho usato ma ho dimenticato che non era un built-in, ma puoi usarlo con questo, pypi.python.org/pypi/cached-property/0.1.5
adarsh

2
Interessante. Penso che sia stato pubblicato per la prima volta dopo questa risposta, ma probabilmente chiunque lo legga dovrebbe usarlo.
Detly

10

Leggendo le risposte e i commenti, il tema principale sembra essere che le risposte sembrano mancare di un esempio semplice, ma utile. Ne ho incluso uno molto semplice qui che dimostra l'uso semplice del @propertydecoratore. È una classe che consente a un utente di specificare e ottenere la misurazione della distanza utilizzando una varietà di unità diverse, ovvero in_feeto in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Uso:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

per me personalmente, i migliori esempi di getter / setter sono di mostrare alle persone il tipo di modifiche che è necessario apportare in seguito, ma ovviamente ciò richiede un po 'più di tempo.
dtc,

7

La proprietà è solo un'astrazione attorno a un campo che offre un maggiore controllo sui modi in cui un campo specifico può essere manipolato e per eseguire calcoli del middleware. Pochi degli usi che vengono in mente sono la convalida, l'inizializzazione e la limitazione dell'accesso precedenti

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

6

Sì, per l'esempio originale pubblicato, la proprietà funzionerà esattamente come avere semplicemente una variabile di istanza 'x'.

Questa è la cosa migliore delle proprietà di Python. Dall'esterno, funzionano esattamente come le variabili di istanza! Ciò consente di utilizzare variabili di istanza esterne alla classe.

Questo significa che il tuo primo esempio potrebbe effettivamente utilizzare una variabile di istanza. Se le cose sono cambiate e decidi di cambiare l'implementazione e una proprietà è utile, l'interfaccia per la proprietà sarebbe comunque la stessa dal codice esterno alla classe. Una modifica dalla variabile di istanza alla proprietà non ha alcun impatto sul codice esterno alla classe.

Molti altri linguaggi e corsi di programmazione indicheranno che un programmatore non dovrebbe mai esporre le variabili di istanza, e utilizzare invece "getter" e "setter" per accedere a qualsiasi valore al di fuori della classe, anche nel caso semplice riportato nella domanda.

Codice esterno alla classe con molte lingue (ad es. Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

E quando si implementa la classe ci sono molti 'getter' e 'setter' che fanno esattamente come il tuo primo esempio: replicare semplicemente una variabile di istanza. Questi getter e setter sono necessari perché se l'implementazione della classe cambia, tutto il codice esterno alla classe dovrà cambiare. Ma le proprietà di Python consentono al codice esterno alla classe di essere uguale a quello delle variabili di istanza. Pertanto, il codice esterno alla classe non deve essere modificato se si aggiunge una proprietà o si dispone di una semplice variabile di istanza. Quindi, diversamente dalla maggior parte dei linguaggi orientati agli oggetti, per il tuo semplice esempio puoi usare la variabile di istanza invece di "getter" e "setter" che non sono realmente necessari, assicurati che se cambi in una proprietà in futuro, il codice usando la tua classe non deve cambiare.

Ciò significa che è necessario creare proprietà solo in presenza di comportamenti complessi e per il caso molto comune in cui, come descritto nella domanda, è sufficiente una semplice variabile di istanza, è possibile utilizzare semplicemente la variabile di istanza.


6

un'altra bella caratteristica delle proprietà rispetto all'utilizzo dei setter e lo ottiene che consentono di continuare a utilizzare OP = operatori (ad es. + =, - =, * = ecc.) sui tuoi attributi mantenendo comunque qualsiasi convalida, controllo degli accessi, memorizzazione nella cache, ecc. che i setter e i getter avrebbero fornito.

ad esempio se hai scritto la classe Personcon un setter setage(newage)e un getter getage(), quindi per aumentare l'età dovresti scrivere:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

ma se hai creato ageuna proprietà potresti scrivere molto più pulito:

bob.age += 1

5

La risposta breve alla tua domanda è che nel tuo esempio non ci sono vantaggi. Probabilmente dovresti usare il modulo che non comporta proprietà.

La ragione per cui esiste la proprietà è che se il codice cambia in futuro e improvvisamente devi fare di più con i tuoi dati: valori della cache, protezione dell'accesso, query di alcune risorse esterne ... qualunque cosa, puoi facilmente modificare la tua classe per aggiungere getter e setter per i dati senza cambiare l'interfaccia, quindi non devi trovare ovunque nel tuo codice dove si accede a quei dati e cambiare anche quello.


4

Qualcosa che molti non notano all'inizio è che puoi creare le tue sottoclassi di proprietà. Questo l'ho trovato molto utile per esporre attributi o attributi di sola lettura che puoi leggere e scrivere ma non rimuovere. È anche un modo eccellente per racchiudere funzionalità come il monitoraggio delle modifiche ai campi degli oggetti.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Mi piace questo. Sto leggendo questo correttamente in quel lettore viene letto solo mentre l'accessor è in lettura / scrittura senza capacità di eliminazione? Come aggiungerebbe la convalida dei dati? Sono abbastanza nuovo per Python, ma sto pensando che c'è probabilmente un modo per aggiungere un callback per la attr = reader('_attr')linea o una qualche forma di prechecking come attr = if self.__isValid(value): reader('_attr'). Suggerimenti?
Gabe Spradlin,

Spiacente, ho appena realizzato che stavo chiedendo la convalida dei dati per una variabile di sola lettura. Ma ovviamente questo si applica solo alla parte setter della classe accessor. Quindi cambia attr = reader('_attr')in attr = accessor('_attr'). Grazie
Gabe Spradlin il

Hai ragione nel dire che se volessi la convalida, aggiungeresti una funzione per convalidare e sollevare l'eccezione se non valida (o qualunque comportamento ti piaccia, incluso non fare nulla) all'init . Ho modificato quanto sopra con un possibile modello. Il validatore dovrebbe restituire True | False per guidare se l'insieme accade o no.
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.