Scopo del "ritorno di sé" da un metodo di classe?


46

Mi sono imbattuto in qualcosa di simile in un progetto open source. I metodi che modificano gli attributi dell'istanza restituiscono un riferimento all'istanza. Qual è lo scopo di questo costrutto?

class Foo(object):

  def __init__(self):
    self.myattr = 0

  def bar(self):
    self.myattr += 1
    return self

2
Ed è così che quasi tutto jQuery è scritto. Quasi ogni funzione restituisce un oggetto jQuery
CaffGeek il

11
Perché non ti fidi del codice scritto in quel modo?
sepp2k,

Risposte:


65

È per consentire il concatenamento.

Per esempio:

var Car = function () {
    return {
        gas : 0,
        miles : 0,
        drive : function (d) {
            this.miles += d;
            this.gas -= d;
            return this;
        },
        fill : function (g) {
            this.gas += g;
            return this;
        },
    };
}

Ora puoi dire:

var c = Car();
c.fill(100).drive(50);
c.miles => 50;
c.gas => 50;

20
per la cronaca, questo tipo di concatenamento è generalmente visto sul codice con un'interfaccia fluida .
Lie Ryan,

10

Come menzionano @Lie Ryan e @Frank Shearar, questa si chiama "interfaccia fluente" ma quel modello esiste da molto tempo.

La parte controversa di quel modello è che in OO hai uno stato mutabile, quindi un metodo vuoto ha una sorta di valore di ritorno implicito this- cioè, l'oggetto con stato aggiornato è tipo del valore di ritorno.

Quindi in una lingua OO con stato mutabile, questi due sono più o meno equivalenti:

a.doA()
a.doB()
a.doC()

... al contrario di

a.doA().doB().doC()

Quindi ho sentito persone in passato resistere alle interfacce fluide perché a loro piace la prima forma. Un altro nome che ho sentito per "interfaccia fluente" è "disastro ferroviario";)

Dico "più o meno equivalenti", tuttavia, perché le interfacce fluide aggiungono una ruga. Non devono "restituire questo". Possono "tornare nuovi". Questo è un modo per raggiungere oggetti immutabili in OO.

Quindi potresti avere una classe A che lo fa (pseudocodice)

function doA():
    return new A(value + 1)

function doB():
    return new A(value * 2)

function doC():
    return new A(sqrt(value))

Ora, ogni metodo restituisce un oggetto nuovo di zecca, lasciando invariato l'oggetto iniziale. E questo è un modo per entrare in oggetti immutabili senza cambiare molto nel tuo codice.


Ok ma se i tuoi metodi ritornano new(...), ciò perderebbe memoria.
smci,

@smci Dipende molto dal linguaggio, dall'implementazione e dall'uso. Certo, se è C ++, probabilmente perderai memoria dappertutto. Ma tutto ciò sarebbe banalmente ripulito da una lingua con un bidone della spazzatura. Alcune implementazioni (con o senza GC) potrebbero persino rilevare quando uno di questi nuovi oggetti non viene utilizzato e non alloca effettivamente nulla.
settembre

@ 8bittree: stiamo parlando di Python, questa domanda è stata taggata Python 8 anni fa. Python GC non è eccezionale, quindi è meglio non perdere memoria in primo luogo. La chiamata __init__ripetuta comporta anche un sovraccarico.
smci,

8

La maggior parte delle lingue è a conoscenza del linguaggio del "sé di ritorno" e lo ignora se non viene utilizzato in una riga. Tuttavia è da notare che in Python le funzioni ritornano Nonedi default.

Quando ero a scuola CS mio istruttore ha fatto un enorme affare sulla differenza tra le funzioni, le procedure, routine e metodi; molte domande sul tema dei crampi alle mani sono state risolte con matite meccaniche che mi si scaldavano nelle mani.

Basti dire che restituire sé è il modo OO definitivo per creare metodi di classe, ma Python consente più valori di ritorno, tuple, elenchi, oggetti, primitive o Nessuno.

Il concatenamento, come dicono, sta semplicemente mettendo la risposta all'ultima operazione nella successiva e il runtime di Python può ottimizzare quel tipo di cose. La comprensione dell'elenco è una forma integrata di questo. (Molto potente!)

Quindi in Python non è così importante che ogni metodo o funzione restituisca cose, motivo per cui il valore predefinito è Nessuno.

Esiste una scuola di pensiero secondo cui ogni azione in un programma dovrebbe riportare il suo successo, fallimento o ricondurre al contesto o all'oggetto che invoca, ma non parlava qui dei requisiti ADA di DOD. Se hai bisogno di ricevere feedback da un metodo, vai avanti o no, ma cerca di essere coerente al riguardo.

Se un metodo può fallire, dovrebbe restituire successo o fallimento o sollevare un'eccezione da gestire.

Un avvertimento è che se si utilizza l'idioma di ritorno, Python ti permetterà di assegnare tutti i tuoi metodi alle variabili e potresti pensare di ottenere un risultato di dati o un elenco quando stai effettivamente ottenendo l'oggetto.

I linguaggi restrittivi dei tipi gridano, gridano e si rompono quando provi a farlo, ma quelli interpretati (Python, Lua, Lisp) sono molto più dinamici.


4

In Smalltalk, ogni metodo che non restituisce esplicitamente qualcosa, ha un implicito "ritorno di sé".

Questo perché (a) avere ogni metodo restituisce qualcosa rende il modello di calcolo più uniforme e (b) è molto utile avere i metodi restituiscono se stessi. Josh K fa un buon esempio.


Interessante ... in Python, ogni metodo che non restituisce esplicitamente qualcosa, ha un implicito "return None".
Giobbe

2

Pro di restituzione di un oggetto ( return self)

  • Esempio:

    print(foo.modify1().modify2())
    
    # instaed of
    foo.modify1()
    foo.modify2()
    print(foo)
    
  • Stile più popolare nella comunità di programmazione (penso).

Pro di mutazione di un oggetto (no return self)

  • Capacità di utilizzare returnper altri scopi.
  • Guido van Rossum approva questo stile (non sono d'accordo con la sua tesi).
  • Stile più popolare nella comunità di Python (penso).
  • Predefinito (meno codice sorgente).

L'argomento di Guido è convincente per me. In particolare la parte in cui parla di "il lettore deve avere un'intima familiarità con ciascuno dei metodi". Vale a dire che ti costringe a determinare se si tratta di un'auto catena o di una catena di output o di una combo prima di capire cosa hai alla fine del chain
Nath

@Nat In che modo le operazioni di elaborazione delle stringhe differiscono sostanzialmente dal resto delle operazioni?
xgedato il

La differenza è che la stringa case sta generando ogni volta un nuovo oggetto completamente diverso. Vale a dire non modificare l'elemento in atto. è chiaro che l'oggetto esistente non viene modificato. In una lingua in cui il sé del ritorno è implicito, è vero il contrario, ma l'ambiente misto sembra problematico
Nath,

@Matt Ho appena ricordato che le stringhe di Python sono immutabili. Questo risponde pienamente alla mia domanda.
xgedato il

1
@Matt "è chiaro che l'oggetto esistente non viene modificato", ma non è chiaro solo dall'aspetto.
xgedato il
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.