Objetivo de "retornar a si mesmo" de um método de classe?

46

Me deparei com algo assim em um projeto de código aberto. Os métodos que modificam os atributos da instância retornam uma referência para a instância. Qual é o objetivo dessa construção?

class Foo(object):

  def __init__(self):
    self.myattr = 0

  def bar(self):
    self.myattr += 1
    return self
nate c
fonte
2
E é assim que praticamente todo o jQuery é escrito. Quase todas as funções retornam um objeto jQuery
CaffGeek
11
Por que você não confiaria em códigos escritos assim?
sepp2k

Respostas:

65

É para permitir o encadeamento.

Por exemplo:

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

Agora você pode dizer:

var c = Car();
c.fill(100).drive(50);
c.miles => 50;
c.gas => 50;
Josh K
fonte
20
para o registro, esse tipo de encadeamento geralmente é visto no código com interface fluente .
Lie Ryan
10

Como mencionam @Lie Ryan e @Frank Shearar, isso é chamado de "interface fluente", mas esse padrão existe há muito tempo.

A parte controversa desse padrão é que, em OO, você tem um estado mutável; portanto, um método nulo possui um tipo de valor de retorno implícito de this- ou seja, o objeto com estado atualizado é o tipo de valor de retorno.

Portanto, em uma linguagem OO com estado mutável, esses dois são mais ou menos equivalentes:

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

... em oposição a

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

Então, ouvi pessoas no passado resistirem a interfaces fluentes porque gostam da primeira forma. Outro nome que ouvi por "interface fluente" é "acidente de trem";)

Eu digo "mais ou menos equivalente", no entanto, porque as interfaces fluentes adicionam uma ruga. Eles não precisam "devolver isso". Eles podem "retornar novos". Essa é uma maneira de obter objetos imutáveis ​​no OO.

Então você pode ter uma classe A que faz (pseudocódigo)

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

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

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

Agora, cada método retorna um novo objeto, mantendo o objeto inicial inalterado. E essa é uma maneira de entrar em objetos imutáveis ​​sem alterar muito o seu código.

Roubar
fonte
Ok, mas se seus métodos retornarem new(...), isso vazará memória.
smci 17/07/2018
@smci Isso dependeria muito do idioma, implementação e uso. Claro, se for C ++, você provavelmente estará vazando memória em todo o lugar. Mas tudo isso seria trivialmente limpo por um idioma com um coletor de lixo. Algumas implementações (com ou sem GC) podem até detectar quando um desses novos objetos não é usado e simplesmente não alocar nada.
8bittree
@ 8bittree: estamos falando de Python, esta pergunta foi marcada como Python há 8 anos. O Python GC não é ótimo, então é melhor não perder memória em primeiro lugar. Ligar __init__repetidamente também gera sobrecarga.
smci
8

A maioria das linguagens conhece o idioma 'auto-retorno' e o ignora se não for usado em uma linha. No entanto, é notável que, em Python, as funções retornem Nonepor padrão.

Quando eu estava na escola de informática, meu instrutor fez um grande acordo sobre a diferença entre funções, procedimentos, rotinas e métodos; muitas perguntas sobre ensaios de aperto de mãos foram feitas com lapiseiras esquentando minhas mãos sobre tudo isso.

Basta dizer que retornar self é a maneira OO definitiva de criar métodos de classe, mas o Python permite vários valores de retorno, tuplas, listas, objetos, primitivos ou Nenhum.

O encadeamento, como eles dizem, é apenas colocar a resposta da última operação na próxima, e o tempo de execução do Python pode otimizar esse tipo de coisa. A compreensão da lista é uma forma interna disso. (Muito poderoso!)

Portanto, no Python, não é tão importante que todo método ou função retorne coisas, e é por isso que o padrão é Nenhum.

Existe uma escola de pensamento de que toda ação em um programa deve relatar seu sucesso, falha ou resultado de volta ao seu contexto ou objeto de chamada, mas não estava falando aqui dos Requisitos do DOD ADA aqui. Se você precisar obter feedback de um método, vá em frente ou não, mas tente ser consistente.

Se um método pode falhar, ele deve retornar com êxito ou falha ou gerar uma exceção a ser tratada.

Uma ressalva é que, se você usar o idioma próprio de retorno, o Python permitirá que você atribua todos os seus métodos a variáveis ​​e você pode pensar que está obtendo um resultado ou uma lista de dados quando realmente está obtendo o objeto.

Linguagens de tipo restritivo gritam e gritam e quebram quando você tenta fazer isso, mas as interpretadas (Python, Lua, Lisp) são muito mais dinâmicas.

Chris Reid
fonte
4

No Smalltalk, todo método que não retorna algo explicitamente tem um "eu de retorno" implícito.

Isso ocorre porque (a) fazer com que todo método retorne algo torna o modelo de computação mais uniforme e (b) é muito útil que os métodos retornem a si próprios. Josh K dá um bom exemplo.

Frank Shearar
fonte
Interessante ... em Python, todo método que não retorna algo explicitamente, tem um "retorno Nenhum" implícito.
Job
2

Prós de retornar um objeto ( return self)

  • Exemplo:

    print(foo.modify1().modify2())
    
    # instaed of
    foo.modify1()
    foo.modify2()
    print(foo)
  • Estilo mais popular na comunidade de programação (eu acho).

Prós de mudar um objeto (não return self)

  • Capacidade de usar returnpara outros fins.
  • Guido van Rossum aprova esse estilo (eu discordo de seu argumento).
  • Estilo mais popular na comunidade Python (eu acho).
  • Padrão (menos código fonte).
xged
fonte
O argumento de Guido é convincente para mim. Particularmente a parte em que ele fala sobre "o leitor deve estar intimamente familiarizado com cada um dos métodos" Ou seja, obriga você a determinar se essa é uma cadeia própria ou uma cadeia de saída ou uma combinação antes de entender o que você tem no final do processo. cadeia
Nath
@ Nat Como as operações de processamento de strings diferem fundamentalmente do restante das operações?
xged
A diferença é que, no caso de string, você está gerando um novo objeto completamente diferente a cada vez. Ou seja, não modificando o item no local. é claro que o objeto existente não está sendo modificado. Em uma linguagem onde o retorno auto está implícito o oposto é verdadeiro, mas o ambiente misto parece problemático
Nath
@ Matt Acabei de lembrar que as seqüências de caracteres Python são imutáveis. Isso responde totalmente à minha pergunta.
xged
1
@ Matt "está claro que o objeto existente não está sendo modificado" - não está claro apenas olhando.
xged