Cel „return self” z metody klasowej?

46

Spotkałem coś takiego w projekcie open source. Metody modyfikujące atrybuty instancji zwracają odwołanie do instancji. Jaki jest cel tego konstruktu?

class Foo(object):

  def __init__(self):
    self.myattr = 0

  def bar(self):
    self.myattr += 1
    return self
Nate C
źródło
2
I tak napisane jest prawie całe jQuery. Niemal każda funkcja zwraca obiekt jQuery
CaffGeek,
11
Dlaczego nie ufasz tak napisanemu kodowi?
sepp2k

Odpowiedzi:

65

Ma to na celu łańcuchowanie.

Na przykład:

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

Teraz możesz powiedzieć:

var c = Car();
c.fill(100).drive(50);
c.miles => 50;
c.gas => 50;
Josh K.
źródło
20
dla przypomnienia, ten rodzaj łączenia jest ogólnie widoczny w kodzie z płynnym interfejsem .
Lie Ryan,
10

Jak wspominają @Lie Ryan i @Frank Shearar, nazywa się to „płynnym interfejsem”, ale ten wzorzec istnieje już od bardzo dawna.

Kontrowersyjna część tego wzorca jest taka, że ​​w OO masz stan zmienny, więc metoda void ma swego rodzaju domniemaną wartość zwracaną this- to znaczy obiekt ze zaktualizowanym stanem jest rodzajem wartości zwracanej.

Tak więc w języku OO ze zmiennym stanem te dwa są mniej więcej równoważne:

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

... w przeciwieństwie do

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

Więc słyszałem, że ludzie w przeszłości opierali się płynnym interfejsom, ponieważ lubią pierwszą formę. Inną nazwą, którą słyszałem dla „płynnego interfejsu”, jest „wrak pociągu”;)

Mówię „mniej więcej równorzędnie”, ponieważ płynne interfejsy powodują zmarszczki. Nie muszą tego „zwracać”. Mogą „zwrócić nowe”. To sposób na osiągnięcie niezmiennych obiektów w OO.

Więc możesz mieć klasę A, która to robi (pseudokod)

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

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

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

Teraz każda metoda zwraca zupełnie nowy obiekt, pozostawiając początkowy obiekt bez zmian. I to jest sposób na dostanie się do niezmiennych obiektów bez wprowadzania znacznych zmian w kodzie.

Obrabować
źródło
Ok, ale jeśli twoje metody powrócą new(...), spowoduje to wyciek pamięci.
smci
@smci To zależy w dużej mierze od języka, implementacji i użytkowania. Jasne, jeśli jest to C ++, prawdopodobnie wycieknie pamięć w każdym miejscu. Ale to wszystko byłoby trywialnie wyczyszczone przez język ze śmieciarzem. Niektóre implementacje (z GC lub bez) mogą nawet wykryć, kiedy jeden z tych nowych obiektów nie jest używany, i po prostu niczego nie przydzielić.
8bittree,
@ 8bittree: mówimy o Python, to pytanie zostało oznaczone Python 8 lat temu. Python GC nie jest świetny, więc najlepiej nie przeciekać pamięci. Wywołanie __init__wielokrotnie wprowadza napowietrznych zbyt.
smci
8

Większość języków jest świadoma idiomu „return self” i ignoruje go, jeśli nie jest używany w wierszu. Warto jednak zauważyć, że w Pythonie funkcje zwracają Nonesię domyślnie.

Kiedy byłem w szkole CS mój instruktor popełnił ogromny wiele o różnicy między funkcji, procedur, procedur i metod; wiele pytań na temat eseju o skurczach dłoni zostało wyrzuconych z ołówków mechanicznych, które rozgrzały się w moich rękach na ten temat.

Wystarczy powiedzieć, że zwracanie siebie jest ostatecznym sposobem OO do tworzenia metod klas, ale Python pozwala na wiele zwracanych wartości, krotek, list, obiektów, prymitywów lub Brak.

Łańcuchy, jak to ujęli, po prostu przekładają odpowiedź na ostatnią operację na następną, a środowisko wykonawcze Pythona może zoptymalizować tego rodzaju rzeczy. Wyjaśnienia list są wbudowaną formą tego. (Bardzo potężny!)

Tak więc w Pythonie nie jest tak ważne, aby każda metoda lub funkcja zwracała rzeczy, dlatego domyślną wartością jest None.

Istnieje szkoła myślenia, że ​​każda akcja w programie powinna raportować swój sukces, niepowodzenie lub wynik z powrotem do kontekstu lub obiektu wywołującego, ale wtedy nie mówiły tutaj o wymaganiach DOD ADA. Jeśli potrzebujesz informacji zwrotnej na temat metody, śmiało bądź nie, ale staraj się być konsekwentny.

Jeśli metoda może zawieść, powinna zwrócić sukces lub porażkę lub zgłosić wyjątek do obsłużenia.

Jednym zastrzeżeniem jest to, że jeśli użyjesz zwrotnego idiomu, Python pozwoli ci przypisać wszystkie twoje metody do zmiennych i możesz pomyśleć, że otrzymujesz wynik danych lub listę, gdy faktycznie otrzymujesz obiekt.

Języki ograniczające pisanie krzyczą, krzyczą i łamią się, gdy próbujesz to zrobić, ale te interpretowane (Python, Lua, Lisp) są znacznie bardziej dynamiczne.

Chris Reid
źródło
4

W Smalltalk każda metoda, która nie zwraca czegoś jawnie, ma ukryte „powrót siebie”.

Wynika to z faktu, że (a) zwracanie każdej metody powoduje, że model komputerowy jest bardziej jednolity, a (b) bardzo przydatne jest, aby metody zwracały siebie. Josh K podaje dobry przykład.

Frank Shearar
źródło
Interesujące ... w Python, każda metoda, która nie zwraca czegoś jawnie, ma domyślny „return None”.
Job
2

Zalety zwracania obiektu ( return self)

  • Przykład:

    print(foo.modify1().modify2())
    
    # instaed of
    foo.modify1()
    foo.modify2()
    print(foo)
  • Bardziej popularny styl w społeczności programistów (tak myślę).

Plusy mutowania obiektu (nie return self)

  • Możliwość wykorzystania returndo innych celów.
  • Guido van Rossum popiera ten styl (nie zgadzam się z jego argumentem).
  • Bardziej popularny styl w społeczności Python (myślę).
  • Domyślnie (mniej kodu źródłowego).
xged
źródło
Argumentacja Guido jest dla mnie przekonująca. W szczególności część, o której mówi: „Czytelnik musi dokładnie zapoznać się z każdą z metod”. To znaczy, że musisz określić, czy jest to łańcuch własny, łańcuch wyjściowy czy kombinacja, zanim zrozumiesz, co masz na końcu łańcuch
Nath,
@Nat Jak operacje przetwarzania łańcucha zasadniczo różnią się od pozostałych operacji?
xged
Różnica polega na tym, że ciąg znaków generuje za każdym razem nowy zupełnie inny obiekt. To znaczy nie modyfikuje przedmiotu na miejscu. jasne jest, że istniejący obiekt nie jest modyfikowany. W języku, w którym jaźń powrotu jest niejawna, prawda jest odwrotna, ale mieszane środowisko wydaje się problematyczne
Nath,
@Matt Właśnie pamiętam, że ciągi Pythona są niezmienne. To w pełni odpowiada na moje pytanie.
xged
1
@Matt „jasne jest, że istniejący obiekt nie jest modyfikowany” - nie jest to jednak oczywiste po prostu patrząc.
xged