But de `return self` à partir d'une méthode de classe?

46

Je suis tombé sur quelque chose comme ça dans un projet open source. Les méthodes qui modifient les attributs d'instance renvoient une référence à l'instance. Quel est le but de cette construction?

class Foo(object):

  def __init__(self):
    self.myattr = 0

  def bar(self):
    self.myattr += 1
    return self
nate c
la source
2
Et voici à peu près tout ce que jQuery a écrit. Presque chaque fonction retourne un objet jQuery
CaffGeek
11
Pourquoi ne pas faire confiance à un code écrit comme ça?
sepp2k

Réponses:

65

C'est pour permettre l'enchaînement.

Par exemple:

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;
        },
    };
}

Maintenant vous pouvez dire:

var c = Car();
c.fill(100).drive(50);
c.miles => 50;
c.gas => 50;
Josh K
la source
20
pour mémoire, ce type de chaînage est généralement utilisé sur du code avec une interface fluide .
Lie Ryan
10

Comme le mentionnent @Lie Ryan et @Frank Shearar, cela s'appelle une "interface fluide", mais ce modèle existe depuis très longtemps.

La partie controversée de ce modèle est que dans OO, vous avez un état mutable, ainsi une méthode void a une sorte de valeur de retour implicite this, c'est-à-dire que l'objet avec l'état updated est une sorte de valeur de retour.

Donc, dans un langage OO avec un état mutable, ces deux sont plus ou moins équivalents:

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

... par opposition à

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

J'ai donc entendu des personnes dans le passé résister à des interfaces fluides parce qu'elles aiment la première forme. Un autre nom que j'ai entendu pour "interface fluide" est "épave de train";)

Je dis "plus ou moins équivalent", cependant, parce que les interfaces fluentes ajoutent une ride. Ils n'ont pas à "rendre ça". Ils peuvent "retourner de nouveau". C'est une façon d'atteindre des objets immuables dans OO.

Donc, vous pourriez avoir une classe A qui fait (pseudocode)

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

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

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

Désormais, chaque méthode retourne un nouvel objet, en laissant l’objet initial inchangé. Et c'est une façon de pénétrer dans des objets immuables sans vraiment changer votre code.

Rob
la source
Ok, mais si vos méthodes reviennent new(...), cela laisserait de la mémoire.
smci
@smci Cela dépendra beaucoup du langage, de la mise en œuvre et de l'utilisation. Bien sûr, si c'est du C ++, vous allez probablement perdre de la mémoire partout. Mais tout cela serait trivialement nettoyé par une langue avec un ramasse-miettes. Certaines implémentations (avec ou sans GC) peuvent même détecter quand l'un de ces nouveaux objets n'est pas utilisé et ne rien allouer.
8bittree
@ 8bittree: nous parlons de Python, cette question a été étiquetée Python il y a 8 ans. Python GC n’est pas génial, il est donc préférable de ne pas perdre de mémoire. L'appel __init__répété introduit également des frais généraux.
smci
8

La plupart des langues sont conscientes de l'idiome 'return self' et l'ignorent si elles ne sont pas utilisées dans une ligne. Cependant, il est à noter qu'en Python, les fonctions sont retournées Nonepar défaut.

Quand j'étais à l'école CS, mon instructeur a énormément parlé de la différence entre les fonctions, les procédures, les routines et les méthodes; beaucoup de questions de dissertation à la main ont été corrigées avec des crayons mécaniques qui me brûlaient les mains à propos de tout ça.

Je me contenterai de dire que le retour à soi-même est la méthode OO définitive pour créer des méthodes de classe, mais Python autorise plusieurs valeurs de retour, tuples, listes, objets, primitives ou Aucune.

Le chaînage, comme ils le disent, met simplement la réponse à la dernière opération dans la suivante, et l'exécution de Python peut optimiser ce genre de chose. Les compréhensions de liste en sont une forme intégrée. (Très puissant!)

Donc, en Python, il n’est pas très important que chaque méthode ou fonction retourne des éléments, raison pour laquelle la valeur par défaut est None.

Il existe une école de pensée selon laquelle chaque action d'un programme devrait rendre compte de son succès, de son échec ou de ses résultats en fonction de son contexte ou de son objet invoquant, mais ne parlait pas des exigences du DOD ADA ici. Si vous avez besoin d'obtenir des commentaires d'une méthode, continuez ou pas, mais essayez d'être cohérent à ce sujet.

Si une méthode peut échouer, elle devrait renvoyer succès ou échec ou générer une exception à gérer.

Un inconvénient est que si vous utilisez l'idiome de self return, Python vous permettra d'affecter toutes vos méthodes à des variables et vous penserez peut-être que vous obtenez un résultat de données ou une liste lorsque vous obtenez réellement l'objet.

Les langages qui restreignent le type crient et crient et se cassent lorsque vous essayez de faire cela, mais ceux interprétés (Python, Lua, Lisp) sont beaucoup plus dynamiques.

Chris Reid
la source
4

Dans Smalltalk, chaque méthode qui ne retourne pas explicitement quelque chose a un "return self" implicite.

Cela est dû au fait que (a) le fait que chaque méthode retourne quelque chose rend le modèle informatique plus uniforme et (b) il est très utile que les méthodes se retournent. Josh K donne un bel exemple.

Frank Shearar
la source
Intéressant ... dans Python, chaque méthode qui ne retourne pas explicitement quelque chose a un "retour Aucun" implicite.
Job
2

Avantages de retourner un objet ( return self)

  • Exemple:

    print(foo.modify1().modify2())
    
    # instaed of
    foo.modify1()
    foo.modify2()
    print(foo)
    
  • Style plus populaire dans la communauté de programmation (je pense).

Avantages de la mutation d'un objet (non return self)

  • Capacité à utiliser returnà d'autres fins.
  • Guido van Rossum approuve ce style (je suis en désaccord avec son argument).
  • Style plus populaire dans la communauté Python (je pense).
  • Par défaut (moins de code source).
xged
la source
L'argument de Guido est convaincant pour moi. En particulier la partie où il parle de "le lecteur doit être intimement familier avec chacune des méthodes", c'est-à-dire qu'il vous oblige à déterminer s'il s'agit d'une chaîne autonome ou d'une chaîne de sortie ou d'un combo avant de comprendre ce que vous avez à la fin du processus. chaîne
Nath
@Nat En quoi les opérations de traitement de chaînes diffèrent-elles fondamentalement du reste des opérations?
glacé
La différence est que la casse de la chaîne génère un nouvel objet complètement différent à chaque fois. C'est-à-dire ne pas modifier l'élément en place. il est clair que l'objet existant n'est pas en cours de modification. Dans une langue où le retour de soi est implicite, l'inverse est vrai mais l'environnement mixte semble problématique
Nath
@ Matt Je viens de me rappeler que les chaînes Python sont immuables. Cela répond pleinement à ma question.
xged
1
@Matt "il est clair que l'objet existant n'est pas en cours de modification" - il ne suffit pas de regarder si.
xged