Qual è il modo pitonico di usare getter e setter?


341

Lo sto facendo come:

def set_property(property,value):  
def get_property(property):  

o

object.property = value  
value = object.property

Sono nuovo di Python, quindi sto ancora esplorando la sintassi e vorrei un consiglio su come farlo.

Risposte:


684

Prova questo: Proprietà Python

Il codice di esempio è:

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

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

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

2
Il setter per x viene chiamato nell'inizializzatore durante l'istanza di _x?
Casey,

7
@Casey: No. I riferimenti a ._x(che non è una proprietà, solo un attributo semplice) ignorano il propertywrapping. Solo riferimenti per .xpassare attraverso property.
ShadowRanger

278

Qual è il modo pitonico di usare getter e setter?

Il modo "Pythonic" non è usare "getter" e "setter", ma usare attributi semplici, come dimostra la domanda, e delper cancellarli (ma i nomi vengono cambiati per proteggere gli innocenti ... builtin):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

Se in seguito, vuoi modificare l'impostazione e ottenere, puoi farlo senza dover modificare il codice utente, usando il propertydecoratore:

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Ogni utilizzo del decoratore copia e aggiorna l'oggetto della proprietà precedente, pertanto è necessario utilizzare lo stesso nome per ogni set, ottenere ed eliminare la funzione / metodo.

Dopo aver definito quanto sopra, l'impostazione originale, il recupero e l'eliminazione del codice sono gli stessi:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Dovresti evitare questo:

def set_property(property,value):  
def get_property(property):  

Innanzitutto, quanto sopra non funziona, perché non fornisci un argomento per l'istanza su cui la proprietà verrebbe impostata (di solito self), che sarebbe:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

In secondo luogo, questo duplica lo scopo di due metodi speciali __setattr__e __getattr__.

In terzo luogo, abbiamo anche le setattre getattrincorporate funzioni.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

Il @propertydecoratore serve per creare getter e setter.

Ad esempio, potremmo modificare il comportamento dell'impostazione per porre restrizioni al valore impostato:

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

In generale, vogliamo evitare di usare propertye usare solo attributi diretti.

Questo è ciò che si aspettano gli utenti di Python. Seguendo la regola della minima sorpresa, dovresti provare a dare ai tuoi utenti ciò che si aspettano, a meno che tu non abbia una ragione molto convincente del contrario.

Dimostrazione

Ad esempio, supponiamo di aver bisogno che l'attributo protetto del nostro oggetto sia un numero intero compreso tra 0 e 100 inclusi e ne prevenga l'eliminazione, con messaggi appropriati per informare l'utente del suo corretto utilizzo:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Si noti che si __init__riferisce a self.protected_valuema i metodi di proprietà fanno riferimento self._protected_value. Questo è così che __init__utilizza la proprietà attraverso l'API pubblica, assicurandosi che sia "protetta".)

E utilizzo:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

I nomi contano?

Sì lo fanno . .settere .deleterfare copie della proprietà originale. Ciò consente alle sottoclassi di modificare correttamente il comportamento senza alterare il comportamento nel genitore.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Ora per far funzionare tutto questo, devi usare i rispettivi nomi:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

Non sono sicuro di dove sarebbe utile, ma il caso d'uso è se si desidera una proprietà get, set e / o delete-only. Probabilmente è meglio attenersi alla stessa proprietà semanticamente con lo stesso nome.

Conclusione

Inizia con attributi semplici.

Se in seguito hai bisogno di funzionalità attorno all'impostazione, alla ricezione e alla cancellazione, puoi aggiungerla con il decoratore di proprietà.

Evita le funzioni denominate set_...e get_...: ecco a cosa servono le proprietà.


Oltre a duplicare le funzionalità disponibili, perché evitare di scrivere i propri setter e getter? Capisco che potrebbe non essere il modo Pythonic, ma ci sono problemi davvero seri che si potrebbero incontrare altrimenti?
user1350191

4
Nella tua demo, il __init__metodo si riferisce a self.protected_valuema il getter e setter si riferiscono self._protected_value. Potresti spiegare come funziona? Ho testato il tuo codice e funziona così com'è, quindi questo non è un errore di battitura.
codeforester,

2
@codeforester Speravo di rispondere prima nella mia risposta, ma fino a quando posso, questo commento dovrebbe essere sufficiente. Spero che tu possa vedere che utilizza la proprietà attraverso l'API pubblica, garantendo che sia "protetta". Non avrebbe senso "proteggerlo" con una proprietà e quindi utilizzare l'API non pubblica invece in __init__esso?
Aaron Hall

2
Sì, @AaronHall ha capito adesso. Non mi rendevo conto che in self.protected_value = start_protected_valuerealtà sta chiamando la funzione setter; Ho pensato che fosse un compito.
codeforester

1
Imho questa dovrebbe essere la risposta accettata, se ho capito correttamente Python prende esattamente il punto opposto rispetto ad esempio a Java. Invece di rendere tutto privato per impostazione predefinita e scrivere del codice aggiuntivo quando è necessario pubblicamente in Python puoi rendere tutto pubblico e aggiungere privacy in seguito
idclev 463035818

27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20

17

L'utilizzo @propertye @attribute.setterti aiuta non solo a utilizzare il modo "pitonico", ma anche a verificare la validità degli attributi sia durante la creazione dell'oggetto che durante la sua modifica.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Con questo, in realtà 'nascondi' _name attributo dagli sviluppatori client ed esegui anche controlli sul tipo di proprietà del nome. Si noti che seguendo questo approccio anche durante l'iniziazione viene chiamato il setter. Così:

p = Person(12)

Porterà a:

Exception: Invalid value for name

Ma:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception

16

Dai un'occhiata al @propertydecoratore .


34
Questa è praticamente una risposta solo link.
Aaron Hall


7
In che modo questa è una risposta completa? Un collegamento non è una risposta.
Andy_A̷n̷d̷y̷,

Penso che sia una buona risposta, perché la documentazione indica chiaramente come usarla (e rimarrà aggiornata se l'implementazione di Python cambia, e indirizza l'OP al metodo che il rispondente sta suggerendo. @ Jean-FrançoisCorbett ha dichiarato chiaramente "come" è una risposta completa.
HunnyBear,

In ogni caso, questa risposta non aggiunge nulla di utile ad altre risposte e idealmente dovrebbe essere eliminata.
Georgy,

5

Puoi usare accessori / mutatori (cioè @attr.settere @property) oppure no, ma la cosa più importante è essere coerenti!

Se stai usando @propertyper accedere semplicemente ad un attributo, ad es

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

usalo per accedere ad ogni * attributo! Sarebbe una cattiva pratica accedere ad alcuni attributi usando @propertye lasciare pubbliche altre proprietà (es. Nome senza trattino basso) senza un accedente, ad es . Non farlo

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

Si noti che qui self.bnon è presente un accesso esplicito anche se è pubblico.

Analogamente ai setter (o ai mutatori ), sentiti libero di usare @attribute.setterma sii coerente! Quando lo fai ad es

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

È difficile per me indovinare la tua intenzione. Da un lato si sta dicendo che sia ae bsono pubblici (nessuna sottolineatura leader nel loro nome) quindi dovrei essere teoricamente permesso di accesso / mutare (get / set) entrambi. Ma poi specifichi un mutatore esplicito solo per a, il che mi dice che forse non dovrei essere in grado di impostare b. Dato che hai fornito un mutatore esplicito, non sono sicuro che la mancanza di un esplicito accessor ( @property) significhi che non dovrei essere in grado di accedere a nessuna di queste variabili o che tu fossi semplicemente frugale nell'utilizzare @property.

* L'eccezione è quando si desidera rendere esplicitamente accessibili o modificabili alcune variabili ma non entrambe o si desidera eseguire una logica aggiuntiva quando si accede o si modifica un attributo. Questo è quando sto usando personalmente @propertye @attribute.setter(altrimenti nessun esplicito acessor / mutatore per attributi pubblici).

Infine, suggerimenti PEP8 e Google Style Guide:

PEP8, Designing for Inheritance dice:

Per semplici attributi di dati pubblici, è meglio esporre solo il nome dell'attributo, senza complicati metodi di accesso / mutatore . Tieni presente che Python fornisce un percorso semplice per il miglioramento futuro, se ritieni che un semplice attributo di dati debba aumentare il comportamento funzionale. In tal caso, utilizzare le proprietà per nascondere l'implementazione funzionale dietro la sintassi di accesso agli attributi di dati semplici.

D'altra parte, secondo la Guida di stile di Google Regole / proprietà del linguaggio Python, la raccomandazione è di:

Usa le proprietà nel nuovo codice per accedere o impostare i dati in cui normalmente avresti utilizzato metodi di accesso o setter semplici e leggeri. Le proprietà dovrebbero essere create con il @propertydecoratore.

I pro di questo approccio:

La leggibilità aumenta aumentando il richiamo esplicito e impostando le chiamate di metodo per un semplice accesso agli attributi. Consente ai calcoli di essere pigri. Considerato il modo Pythonic per mantenere l'interfaccia di una classe. In termini di prestazioni, consentire le proprietà elude la necessità di metodi di accesso banali quando è ragionevole un accesso diretto alla variabile. Ciò consente anche di aggiungere metodi di accesso in futuro senza interrompere l'interfaccia.

e contro:

Deve ereditare da objectin Python 2. Può nascondere effetti collaterali come il sovraccarico dell'operatore. Può essere fonte di confusione per le sottoclassi.


1
Non sono assolutamente d'accordo. Se ho 15 attributi sul mio oggetto e ne voglio uno con cui calcolare @property, fare anche il resto @propertysembra una decisione sbagliata.
Quelklef,

Accetto ma solo se c'è qualcosa di specifico in questo particolare attributo di cui hai bisogno @property(ad es. Eseguendo una logica speciale prima di restituire un attributo). Altrimenti perché dovresti decorare un attributo con @properye non altri?
Tomasz Bartkowiak,

@Quelklef vede il sidenote nel post (contrassegnato da un asterisco).
Tomasz Bartkowiak, il

Bene ... Se non stai facendo una delle cose menzionate dal sidenote, allora non dovresti usare @propertyper cominciare, giusto? Se il tuo getter lo è return this._xe il tuo setter lo è this._x = new_x, allora l'utilizzo @propertyè un po 'sciocco.
Quelklef,

1
Hmm, forse. Personalmente direi che non va bene --- è del tutto superfluo. Ma posso vedere da dove vieni. Immagino di aver appena letto il tuo post dicendo che "la cosa più importante quando si usa @propertyè la coerenza".
Quelklef,

-1

Puoi usare i metodi magici __getattribute__e __setattr__.

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

Sii consapevole __getattr__e __getattribute__non sono gli stessi. __getattr__viene invocato solo quando l'attributo non viene trovato.

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.