python - Usando @property contro getter e setter




6 Answers

In Python non usi getter o setter o proprietà solo per il gusto di farlo. Prima devi solo usare gli attributi e poi, solo se necessario, migrare alla proprietà senza dover cambiare il codice usando le tue classi.

Esiste un sacco di codice con estensione .py che usa getter e setter e classi di ereditarietà e inutili ovunque, ad esempio una semplice tupla, ma è il codice di persone che scrivono in C ++ o Java usando Python.

Quello non è il codice Python.

python properties getter-setter

Ecco una domanda di design specifica per Python:

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

e

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

Python ci consente di farlo in entrambi i modi. Se dovessi progettare un programma Python, quale approccio useresti e perché?




La risposta breve è: proprietà vince a mani basse. Sempre.

A volte c'è bisogno di getter e setter, ma anche allora, li "nascondo" al mondo esterno. Ci sono molti modi per farlo in Python ( getattr , setattr , __getattribute__ , etc ..., ma uno molto conciso e pulito è:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Ecco un breve articolo che introduce l'argomento di getter e setter in Python.




Penso che entrambi abbiano il loro posto. Un problema con l'uso di @property è che è difficile estendere il comportamento di getter o setter in sottoclassi usando meccanismi di classe standard. Il problema è che le funzioni getter / setter sono nascoste nella proprietà.

Puoi effettivamente ottenere le funzioni, ad es. Con

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

è possibile accedere alle funzioni getter e setter come Cpfget e Cpfset , ma non è possibile utilizzare facilmente l'ereditarietà del metodo normale (ad es. super) per estenderli. Dopo aver scavato nella complessità di super, puoi davvero usare super in questo modo:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

L'utilizzo di super () è, tuttavia, abbastanza complesso, poiché la proprietà deve essere ridefinita, e devi usare il meccanismo leggermente super-intuitivo (cls, cls) per ottenere una copia non associata di p.




Preferirei non usare né nella maggior parte dei casi. Il problema con le proprietà è che rendono la classe meno trasparente. Soprattutto, questo è un problema se si dovesse sollevare un'eccezione da un setter. Ad esempio, se si dispone di una proprietà Account.email:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

quindi l'utente della classe non si aspetta che l'assegnazione di un valore alla proprietà possa causare un'eccezione:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

Di conseguenza, l'eccezione potrebbe non essere gestita e propagarsi troppo in alto nella catena di chiamata per essere gestita correttamente, o portare a un traceback molto inutile presentato all'utente del programma (che è tristemente troppo comune nel mondo di Python e Java) ).

Eviterei anche l'uso di getter e setter:

  • perché definirli in anticipo per tutte le proprietà richiede molto tempo,
  • rende la quantità di codice inutilmente più lunga, il che rende più difficile la comprensione e la manutenzione del codice,
  • se le definissi come proprietà solo se necessario, l'interfaccia della classe cambierebbe, danneggiando tutti gli utenti della classe

Invece di proprietà e getter / setter preferisco fare la logica complessa in posti ben definiti come in un metodo di validazione:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

o un metodo Account.save simile.

Nota che non sto cercando di dire che non ci sono casi in cui le proprietà sono utili, solo che potresti stare meglio se puoi rendere le tue lezioni semplici e trasparenti abbastanza da non averne bisogno.




Sono sorpreso che nessuno abbia menzionato che le proprietà sono metodi vincolati di una classe descrittore, e prendono esattamente questa idea nei loro post - che i getter e i setter sono funzioni e possono essere usati per:

  • convalidare
  • alterare i dati
  • tipo di anatra (tipo di coercizione ad un altro tipo)

Questo presenta un modo intelligente per nascondere dettagli di implementazione e code cruft come espressioni regolari, cast di tipi, try ... eccetto blocchi, asserzioni o valori calcolati.

In generale, fare il CRUD su un oggetto può spesso essere abbastanza banale ma considerare l'esempio di dati che verranno mantenuti in un database relazionale. Gli ORM possono nascondere i dettagli di implementazione di particolari vernacoli SQL nei metodi associati a fget, fset, fdel definiti in una classe di proprietà che gestirà le terribili if .. elif .. else ladder che sono così brutte nel codice OO - esponendo il semplice e elegante self.variable = something e self.variable = something i dettagli per lo sviluppatore utilizzando l'ORM.

Se si considerano le proprietà solo come alcune deprimenti vestigia di un linguaggio Bondage e Discipline (cioè Java), mancano il punto dei descrittori.




Sia @property che i getter e setter tradizionali hanno i loro vantaggi. Dipende dal tuo caso d'uso.

Vantaggi di @property

  • Non è necessario modificare l'interfaccia mentre si modifica l'implementazione dell'accesso ai dati. Quando il tuo progetto è piccolo, probabilmente vuoi usare l'accesso diretto agli attributi per accedere a un membro della classe. Ad esempio, supponiamo di avere un oggetto foo di tipo Foo , che ha un numero membro. Quindi puoi semplicemente ottenere questo membro con num = foo.num . Man mano che il tuo progetto cresce, potresti avere bisogno di alcuni controlli o debug sull'accesso semplice agli attributi. Quindi puoi farlo con una proprietà @property all'interno della classe. L'interfaccia di accesso ai dati rimane la stessa così che non è necessario modificare il codice client.

    Citato da PEP-8 :

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

  • L'uso di @property per l'accesso ai dati in Python è considerato come Pythonic :

    • Può rafforzare la tua autoidentificazione come programmatore Python (non Java).

    • Può aiutare il tuo colloquio di lavoro se il tuo intervistatore pensa che i getter e i setter in stile Java siano anti-patterns .

Vantaggi dei getter e setter tradizionali

  • I getter e setter tradizionali consentono un accesso ai dati più complicato rispetto all'accesso semplice degli attributi. Ad esempio, quando si imposta un membro della classe, a volte è necessario un flag che indica dove si desidera forzare questa operazione anche se qualcosa non sembra perfetto. Mentre non è ovvio come aumentare un accesso diretto ai membri come foo.num = num , puoi facilmente aumentare il tuo setter tradizionale con un parametro force aggiuntivo:

    def Foo:
        def set_num(self, num, force=False):
            ...
    
  • I getter e setter tradizionali rendono esplicito che l'accesso di un membro della classe avviene attraverso un metodo. Questo significa:

    • Quello che ottieni come risultato potrebbe non essere lo stesso di quello che è esattamente archiviato all'interno di quella classe.

    • Anche se l'accesso sembra un semplice accesso agli attributi, le prestazioni possono variare notevolmente.

    A meno che gli utenti della classe non si aspettino che @property nasconda dietro ogni dichiarazione di accesso agli attributi, rendere esplicite queste cose può aiutare a minimizzare le sorprese degli utenti della classe.

  • Come menzionato da share e in questo post , l'estensione dei getter e setter tradizionali in sottoclassi è più semplice dell'estensione delle proprietà.

  • I getter e setter tradizionali sono stati ampiamente utilizzati da molto tempo in diverse lingue. Se nella tua squadra ci sono persone con background diversi, hanno un aspetto più familiare di @property .Inoltre, man mano che il progetto cresce, se è necessario migrare da Python ad un'altra lingua che non ha @property, l'uso di getter e setter tradizionali renderebbe la migrazione più fluida.

Avvertenze

  • @propertyi getter e setter tradizionali rendono privato il membro della classe, anche se si usa il doppio trattino basso prima del suo nome:

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0
    



Related