Propósito de 'return self' de un método de clase?

46

Encontré algo como esto en un proyecto de código abierto. Los métodos que modifican los atributos de la instancia devuelven una referencia a la instancia. ¿Cuál es el propósito de esta construcción?

class Foo(object):

  def __init__(self):
    self.myattr = 0

  def bar(self):
    self.myattr += 1
    return self
nate c
fuente
2
Y así es como casi todo jQuery está escrito. Casi todas las funciones devuelven un objeto jQuery
CaffGeek
11
¿Por qué no confiarías en un código escrito así?
sepp2k

Respuestas:

65

Es para permitir el encadenamiento.

Por ejemplo:

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

Ahora puedes decir:

var c = Car();
c.fill(100).drive(50);
c.miles => 50;
c.gas => 50;
Josh K
fuente
20
Para el registro, este tipo de encadenamiento generalmente se ve en el código con una interfaz fluida .
Lie Ryan
10

Como mencionan @Lie Ryan y @Frank Shearar, eso se llama una "interfaz fluida", pero ese patrón ha existido durante mucho tiempo.

La parte controvertida de ese patrón es que en OO, tiene un estado mutable, por lo que un método vacío tiene una especie de valor de retorno implícito this, es decir, el objeto con estado actualizado es una especie de valor de retorno.

Entonces, en un lenguaje OO con estado mutable, estos dos son más o menos equivalentes:

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

...Opuesto a

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

Así que he escuchado a personas en el pasado resistirse a las interfaces fluidas porque les gusta la primera forma. Otro nombre que he escuchado para "interfaz fluida" es "choque de trenes";)

Sin embargo, digo "más o menos equivalente" porque las interfaces fluidas agregan una arruga. No tienen que "devolver esto". Pueden "volver nuevo". Esa es una forma de lograr objetos inmutables en OO.

Entonces podría tener una clase A que sí (pseudocódigo)

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

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

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

Ahora, cada método devuelve un nuevo objeto, dejando el objeto inicial sin cambios. Y esa es una forma de entrar en objetos inmutables sin realmente cambiar mucho en su código.

Robar
fuente
Ok, pero si sus métodos regresan new(...), eso perdería memoria.
smci
@smci Eso dependería en gran medida del lenguaje, la implementación y el uso. Claro, si es C ++, es probable que pierdas memoria por todas partes. Pero todo esto sería trivialmente limpiado por un idioma con un recolector de basura. Algunas implementaciones (con o sin GC) pueden incluso detectar cuando uno de esos nuevos objetos no se utilizan y simplemente no asignan nada.
8bittree
@ 8bittree: estamos hablando de Python, esta pregunta fue etiquetada Python hace 8 años. Python GC no es genial, por lo que es mejor no perder memoria en primer lugar. Llamar __init__repetidamente introduce gastos generales también.
smci
8

La mayoría de los idiomas son conscientes del idioma de "retorno de sí mismo", y lo ignoran si no se usa en una línea. Sin embargo, es notable que en Python, las funciones regresan Nonepor defecto.

Cuando estaba en la escuela CS, mi instructor hizo un gran trato sobre la diferencia entre funciones, procedimientos, rutinas y métodos; muchas preguntas de ensayo sobre calambres en las manos se resolvieron con lápices mecánicos que se calentaron en mis manos sobre todo eso.

Baste decir que devolver self es la forma definitiva de OO de crear métodos de clase, pero Python permite múltiples valores de retorno, tuplas, listas, objetos, primitivas o Ninguno.

Encadenar, como lo expresaron, es simplemente poner la respuesta a la última operación en la siguiente, y el tiempo de ejecución de Python puede optimizar ese tipo de cosas. Las comprensiones de listas son una forma incorporada de esto. (¡Muy poderoso!)

Por lo tanto, en Python no es tan importante que cada método o función devuelva cosas, por lo que el valor predeterminado es Ninguno.

Hay una escuela de pensamiento de que cada acción en un programa debe informar su éxito, fracaso o resultado a su contexto u objeto de invocación, pero luego no estaban hablando de los requisitos DOD ADA aquí. Si necesita obtener comentarios de un método, continúe o no, pero trate de ser coherente al respecto.

Si un método puede fallar, debe devolver el éxito o el fracaso o generar una excepción para ser manejado.

Una advertencia es que si usa el modismo propio de retorno, Python le permitirá asignar todos sus métodos a variables y podría pensar que está obteniendo un resultado de datos o una lista cuando realmente está obteniendo el objeto.

Los lenguajes de tipo restrictivo gritan, gritan y se rompen cuando intenta hacer esto, pero los interpretados (Python, Lua, Lisp) son mucho más dinámicos.

Chris Reid
fuente
4

En Smalltalk, cada método que no devuelve algo explícitamente tiene un "retorno propio" implícito.

Esto se debe a que (a) hacer que cada método devuelva algo hace que el modelo informático sea más uniforme y (b) es muy útil que los métodos se devuelvan a sí mismos. Josh K da un buen ejemplo.

Frank Shearar
fuente
Interesante ... en Python, cada método que no devuelve algo explícitamente, tiene un "retorno ninguno" implícito.
Trabajo
2

Pros de devolver un objeto ( return self)

  • Ejemplo:

    print(foo.modify1().modify2())
    
    # instaed of
    foo.modify1()
    foo.modify2()
    print(foo)
    
  • Estilo más popular en la comunidad de programación (creo).

Ventajas de mutar un objeto (no return self)

  • Capacidad de uso returnpara otros fines.
  • Guido van Rossum aprueba este estilo (no estoy de acuerdo con su argumento).
  • Estilo más popular en la comunidad Python (creo).
  • Predeterminado (menos código fuente).
xged
fuente
El argumento de Guido es convincente para mí. Particularmente la parte en la que habla sobre "el lector debe estar íntimamente familiarizado con cada uno de los métodos" Es decir, obliga a determinar si se trata de una cadena propia o una cadena de salida o un combo antes de comprender lo que tiene al final de la secuencia. cadena
Nath
@Nat ¿Cómo difieren fundamentalmente las operaciones de procesamiento de cadenas del resto de las operaciones?
enviado el
La diferencia es que el caso de cadena está generando un nuevo objeto completamente diferente cada vez. Es decir, no modifica el artículo en su lugar. Está claro que el objeto existente no se está modificando. En un lenguaje donde return self está implícito, lo contrario es cierto, pero el entorno mixto parece problemático
Nath
@ Matt Recién recordé que las cadenas de Python son inmutables. Eso responde completamente a mi pregunta.
enviado el
1
@Matt "está claro que el objeto existente no se está modificando"; sin embargo, no está claro solo por mirar.
enviado el