it-swarm.com.de

Warum werden Verknüpfungen wie x + = y als bewährte Methode angesehen?

Ich habe keine Ahnung, wie diese eigentlich heißen, aber ich sehe sie die ganze Zeit. Die Python Implementierung ist ungefähr so:

x += 5 als Kurzschreibweise für x = x + 5.

Aber warum wird dies als gute Praxis angesehen? Ich bin in fast jedem Buch oder Programmier-Tutorial darauf gestoßen, das ich für Python, C, R usw. gelesen habe. Ich verstehe, dass es praktisch ist, drei Tastenanschläge einschließlich Leerzeichen zu sparen. Aber sie scheinen mich immer zu stolpern, wenn ich Code lese, und zumindest meiner Meinung nach machen sie weniger lesbar, nicht mehr.

Vermisse ich einen klaren und offensichtlichen Grund, warum diese überall verwendet werden?

309
Fomite

Es ist keine Abkürzung.

Das Symbol += Erschien in den 1970er Jahren in der Sprache C und - mit der C-Idee des "Smart Assembler" entspricht ein deutlich anderer Maschinenbefehls- und Adressierungsmodus:

Dinge wie "i=i+1", "i+=1 "Und" ++i "Entsprechen zwar auf abstrakter Ebene den gleichen Effekt, entsprechen aber auf niedriger Ebene einer anderen Arbeitsweise der Prozessor.

Insbesondere diese drei Ausdrücke, vorausgesetzt, die Variable i befindet sich in der in einem CPU-Register gespeicherten Speicheradresse (nennen wir sie D - betrachten Sie sie als "Zeiger auf int") und - ALU des Prozessors nimmt einen Parameter und gibt ein Ergebnis in einem "Akkumulator" zurück (nennen wir es A - denken Sie daran als int).

Mit diesen Einschränkungen (sehr häufig bei allen Mikroprozessoren aus dieser Zeit) wird die Übersetzung höchstwahrscheinlich sein

;i = i+1;
MOV A,(D); //Move in A the content of the memory whose address is in D
ADD A, 1;  //The addition of an inlined constant
MOV (D) A; //Move the result back to i (this is the '=' of the expression)

;i+=1;
ADD (D),1; //Add an inlined constant to a memory address stored value

;++i;
INC (D); //Just "tick" a memory located counter

Der erste Weg, dies zu tun, ist nicht optimal, aber allgemeiner, wenn mit Variablen anstelle von Konstanten (ADD A, B Oder ADD A, (D+x)) gearbeitet wird oder wenn komplexere Ausdrücke übersetzt werden (alle laufen in Push low zusammen Prioritätsoperation in einem Stapel, Aufruf der hohen Priorität, Pop und Wiederholung, bis alle Argumente beseitigt wurden).

Die zweite ist typischer für "Zustandsmaschine": Wir "bewerten nicht länger einen Ausdruck", sondern "arbeiten einen Wert": Wir verwenden weiterhin die ALU, vermeiden jedoch das Verschieben von Werten, da dies das Ergebnis ist, das den Parameter ersetzen darf. Diese Art von Anweisung kann nicht verwendet werden, wenn kompliziertere Ausdrücke erforderlich sind: i = 3*i + i-2 Kann nicht an Ort und Stelle ausgeführt werden, da i mehrmals erforderlich ist.

Die dritte - sogar einfachere - berücksichtigt nicht einmal die Idee der "Addition", sondern verwendet eine "primitivere" (im rechnerischen Sinne) Schaltung für einen Zähler. Der Befehl ist kurzgeschlossen, wird schneller geladen und sofort ausgeführt, da das kombinatorische Netzwerk, das zum Nachrüsten eines Registers erforderlich ist, um es zu einem Zähler zu machen, kleiner und damit schneller als das eines Volladdierers ist.

Mit modernen Compilern (siehe jetzt C), die die Compileroptimierung ermöglichen, kann die Korrespondenz nach Belieben ausgetauscht werden, aber es gibt immer noch einen konzeptionellen Unterschied in der Semantik.

x += 5 Bedeutet

  • Suchen Sie den durch x gekennzeichneten Ort
  • Fügen Sie 5 hinzu

Aber x = x + 5 Bedeutet:

  • Bewerten Sie x + 5
    • Suchen Sie den durch x gekennzeichneten Ort
    • Kopiere x in einen Akku
    • 5 zum Akkumulator hinzufügen
  • Speichern Sie das Ergebnis in x
    • Suchen Sie den durch x gekennzeichneten Ort
    • Kopieren Sie den Akku darauf

Natürlich kann die Optimierung

  • wenn "Finden von x" keine Nebenwirkungen hat, können die beiden "Finden" einmal durchgeführt werden (und x wird zu einer Adresse, die in einem Zeigerregister gespeichert ist).
  • die beiden Kopien können entfernt werden, wenn ADD auf &x anstatt auf den Akkumulator angewendet wird

dadurch stimmt der optimierte Code mit dem x += 5 überein.

Dies ist jedoch nur möglich, wenn "x finden" ansonsten keine Nebenwirkungen hat

*(x()) = *(x()) + 5;

und

*(x()) += 5;

sind semantisch unterschiedlich, da x() Nebenwirkungen (zugeben, dass x() eine Funktion ist, die seltsame Dinge erledigt und einen int* zurückgibt) zweimal oder einmal erzeugt werden.

Die Äquivalenz zwischen x = x + y Und x += y Ist daher auf den besonderen Fall zurückzuführen, in dem += Und = Auf einen direkten l-Wert angewendet werden.

Um zu Python zu wechseln, hat es die Syntax von C geerbt, aber da es vor der Ausführung in interpretierten Sprachen keine Übersetzung/Optimierung gibt, sind die Dinge nicht unbedingt so eng miteinander verbunden (da es einen Parsing-Schritt weniger gibt). Ein Interpreter kann sich jedoch auf verschiedene Ausführungsroutinen für die drei Ausdruckstypen beziehen und dabei je nach Art des Ausdrucks und Auswertungskontext unterschiedliche Maschinencodes nutzen.


Für diejenigen, die mehr Details mögen ...

Jede CPU verfügt über eine ALU (arithmetisch-logische Einheit), die im Wesentlichen ein kombinatorisches Netzwerk ist, dessen Ein- und Ausgänge abhängig vom Opcode des Befehls mit den Registern und/oder dem Speicher "verbunden" sind.

Binäre Operationen werden typischerweise als "Modifikator eines Akkumulatorregisters mit einer Eingabe" irgendwo "implementiert, wo sich irgendwo - innerhalb des Befehlsflusses selbst (typisch für Manifest Contant: ADD A 5) - innerhalb eines anderen Registers befinden kann (typisch für die Ausdrucksberechnung mit Provisorien: zB ADD AB) - im Speicher an einer durch ein Register angegebenen Adresse (typisch für das Abrufen von Daten, zB: ADD A (H)) - H funktioniert in diesem Fall wie ein Dereferenzierungszeiger.

Mit diesem Pseudocode ist x += 5

ADD (X) 5

während x = x+5 ist

MOVE A (X)
ADD A 5
MOVE (X) A

Das heißt, x + 5 gibt eine temporäre Datei an, die später zugewiesen wird. x += 5 Arbeitet direkt auf x.

Die tatsächliche Implementierung hängt vom tatsächlichen Befehlssatz des Prozessors ab: Wenn kein ADD (.) c - Opcode vorhanden ist, wird der erste Code zum zweiten: no way.

Wenn es einen solchen Opcode gibt und die Optimierung aktiviert ist, wird der zweite Ausdruck nach Eliminieren der Rückwärtsbewegungen und Anpassen des Register-Opcodes der erste.

603

Je nachdem, wie Sie darüber denken, ist es tatsächlich einfacher zu verstehen, weil es einfacher ist. Nehmen Sie zum Beispiel:

x = x + 5 ruft die mentale Verarbeitung von "nimm x, füge fünf hinzu und weise diesen neuen Wert dann wieder x zu" auf.

x += 5 kann als "x um 5 erhöhen" angesehen werden

Es ist also nicht nur eine Abkürzung, es beschreibt die Funktionalität viel direkter. Beim Lesen von Code-Gobs ist es viel einfacher zu verstehen.

285
Eric King

Zumindest in Python , x += y und x = x + y kann ganz andere Dinge tun.

Zum Beispiel, wenn wir es tun

a = []
b = a

dann a += [3] wird darin enden, dass a == b == [3], während a = a + [3] wird darin enden, dass a == [3] und b == []. Das ist, += ändert das Objekt an Ort und Stelle (nun, es kann sein, dass Sie das __iadd__ Methode, um so ziemlich alles zu tun, was du willst), während = erstellt ein neues Objekt und bindet die Variable daran.

Dies ist sehr wichtig, wenn Sie numerische Arbeiten mit NumPy ausführen, da Sie häufig mehrere Verweise auf verschiedene Teile eines Arrays haben und es wichtig ist, sicherzustellen, dass Sie nicht versehentlich einen Teil eines Arrays ändern dass es andere Verweise auf Arrays gibt oder diese unnötig kopiert (was sehr teuer sein kann).

50
James

Es heißt Redewendung . Programmiersprachen sind nützlich, weil sie eine konsistente Art sind, ein bestimmtes Programmierkonstrukt zu schreiben.

Wann immer jemand schreibt x += y Sie wissen, dass x um y und nicht um eine komplexere Operation erhöht wird (als bewährte Methode würde ich normalerweise keine komplizierteren Operationen und diese Syntaxkürzel mischen). Dies ist am sinnvollsten, wenn Sie um 1 erhöhen.

43
Joe

Um @ Pubbys Punkt etwas klarer zu formulieren, betrachten Sie someObj.foo.bar.func(x, y, z).baz += 5

Ohne den Operator += Gibt es zwei Möglichkeiten:

  1. someObj.foo.bar.func(x, y, z).baz = someObj.foo.bar.func(x, y, z).baz + 5. Dies ist nicht nur furchtbar redundant und lang, sondern auch langsamer. Deshalb müsste man
  2. Verwenden Sie eine temporäre Variable: tmp := someObj.foo.bar.func(x, y, z); tmp.baz = tmp.bar + 5. Das ist in Ordnung, aber für eine einfache Sache ist es viel Lärm. Dies ist eigentlich sehr ähnlich zu dem, was zur Laufzeit passiert, aber es ist mühsam zu schreiben und nur += Zu verwenden, wird die Arbeit auf den Compiler/Interpreter verlagern.

Der Vorteil von += Und anderen solchen Operatoren ist unbestreitbar, während es nur eine Frage der Zeit ist, sich an sie zu gewöhnen.

42
back2dos

Es ist wahr, dass es kürzer und einfacher ist, und es ist wahr, dass es wahrscheinlich von der zugrunde liegenden Assemblersprache inspiriert wurde, aber der Grund dafür ist Best Practice, dass es eine ganze Klasse von Fehlern verhindert und es einfacher macht um den Code zu überprüfen und sicher zu sein, was er tut.

Mit

RidiculouslyComplexName += 1;

Da es sich nur um einen Variablennamen handelt, sind Sie sicher, was die Anweisung bewirkt.

Mit RidiculouslyComplexName = RidiculosulyComplexName + 1;

Es gibt immer Zweifel, dass die beiden Seiten genau gleich sind. Hast du den Fehler gesehen? Es wird noch schlimmer, wenn Indizes und Qualifikationsmerkmale vorhanden sind.

21
Jamie Cox

Während die + = Notation idiomatisch und kürzer ist, sind dies nicht die Gründe, warum es einfacher zu lesen ist. Der wichtigste Teil beim Lesen von Code ist die Zuordnung der Syntax zur Bedeutung. Je näher die Syntax den Denkprozessen des Programmierers entspricht, desto besser ist sie lesbar (dies ist auch der Grund, warum Boilerplate-Code schlecht ist: Er ist nicht Teil des Gedankens Prozess, aber immer noch notwendig, um den Code funktionsfähig zu machen). In diesem Fall lautet der Gedanke "Inkrementvariable x um 5", nicht "x sei der Wert von x plus 5".

Es gibt andere Fälle, in denen eine kürzere Notation die Lesbarkeit beeinträchtigt, z. B. wenn Sie einen ternären Operator verwenden, bei dem eine if -Anweisung besser geeignet wäre.

14
tdammers

Für einen Einblick, warum diese Operatoren zunächst in den Sprachen im C-Stil sind, gibt es diesen Auszug aus K & R 1st Edition (1978), vor 34 Jahren:

Abgesehen von der Prägnanz haben Zuweisungsoperatoren den Vorteil, dass sie besser der Denkweise der Menschen entsprechen. Wir sagen "addiere 2 zu i" oder "erhöhe i um 2", nicht "nimm i, addiere 2 und setze das Ergebnis wieder in i". Also i += 2. Darüber hinaus für einen komplizierten Ausdruck wie

yyval[yypv[p3+p4] + yypv[p1+p2]] += 2

der Zuweisungsoperator erleichtert das Verständnis des Codes, da der Leser nicht sorgfältig prüfen muss, ob zwei lange Ausdrücke tatsächlich gleich sind, oder sich fragen muss, warum dies nicht der Fall ist. Ein Zuweisungsoperator kann dem Compiler sogar helfen, effizienteren Code zu erstellen.

Ich denke, aus dieser Passage geht hervor, dass Brian Kernighan und Dennis Ritchie (K & R) glaubten, dass zusammengesetzte Zuweisungsoperatoren die Lesbarkeit von Code verbessern.

Es ist lange her, dass K & R das geschrieben hat, und viele der „Best Practices“, wie Menschen Code schreiben sollten, haben sich seitdem geändert oder weiterentwickelt. Aber diese Frage von programmers.stackexchange ist das erste Mal, dass ich mich an jemanden erinnern kann, der eine Beschwerde über die Lesbarkeit zusammengesetzter Zuweisungen geäußert hat. Ich frage mich also, ob viele Programmierer sie als Problem empfinden. Andererseits, während ich dies tippe, hat die Frage 95 positive Stimmen, so dass die Leute sie vielleicht beim Lesen von Code als störend empfinden.

12
Michael Burr

Neben der Lesbarkeit machen sie tatsächlich verschiedene Dinge: += muss seinen linken Operanden nicht zweimal auswerten.

Zum Beispiel, expr = expr + 5 würde expr zweimal auswerten (vorausgesetzt, expr ist unrein).

9
Pubby

Abgesehen von den offensichtlichen Vorzügen, die andere Leute sehr gut beschrieben haben, ist es kompakter, wenn Sie sehr lange Namen haben.

  MyVeryVeryVeryVeryVeryLongName += 1;

oder

  MyVeryVeryVeryVeryVeryLongName =  MyVeryVeryVeryVeryVeryLongName + 1;
6

Es ist prägnant.

Es ist viel kürzer zu tippen. Es sind weniger Bediener beteiligt. Es hat weniger Oberfläche und weniger Verwechslungsgefahr.

Es wird ein spezifischerer Operator verwendet.

Dies ist ein erfundenes Beispiel, und ich bin mir nicht sicher, ob tatsächliche Compiler dies implementieren. x + = y verwendet tatsächlich ein Argument und einen Operator und ändert x an Ort und Stelle. x = x + y könnte eine Zwischendarstellung von x = z haben, wobei z x + y ist. Letzteres verwendet zwei Operatoren, Addition und Zuweisung, sowie eine temporäre Variable. Der einzelne Operator macht sehr deutlich, dass die Werteseite nichts anderes als y sein kann und nicht interpretiert werden muss. Und theoretisch könnte es eine ausgefallene CPU geben, die einen Plus-Gleichheits-Operator hat, der schneller läuft als ein Plus-Operator und einen Zuweisungsoperator in Reihe.

6
Mark Canlas

Es ist eine nette Redewendung. Ob es schneller ist oder nicht, hängt von der Sprache ab. In C ist es schneller, weil es in eine Anweisung übersetzt wird, die Variable auf der rechten Seite zu erhöhen. Moderne Sprachen, einschließlich Python, Ruby, C, C++ und Java, alle unterstützen die Syntax op =. Sie ist kompakt und Sie gewöhnen sich schnell daran. Da Sie sie in anderen Sprachen häufig sehen werden Menschencode (OPC), Sie können sich genauso gut daran gewöhnen und ihn verwenden. Hier ist, was in einigen anderen Sprachen passiert.

Geben Sie in Python x += 5 bewirkt immer noch die Erstellung des Integer-Objekts 1 (obwohl es möglicherweise aus einem Pool stammt) und die Verwaiste des Integer-Objekts, das 5 enthält.

In Java kommt es zu einer stillschweigenden Umwandlung. Versuchen Sie es mit Tippen

int x = 4;
x = x + 5.2  // This causes a compiler error
x += 5.2     // This is not an error; an implicit cast is done.
5
ncmathsadist

Operatoren wie += sind sehr nützlich, wenn Sie eine Variable als Akkumulator verwenden, d. h. als laufende Summe:

x += 2;
x += 5;
x -= 3;

Ist viel einfacher zu lesen als:

x = x + 2;
x = x + 5;
x = x - 3;

Im ersten Fall ändern Sie konzeptionell den Wert in x. Im zweiten Fall berechnen Sie einen neuen Wert und weisen ihn jedes Mal x zu. Und während Sie wahrscheinlich nie so einfachen Code schreiben würden, bleibt die Idee dieselbe ... Der Fokus liegt darauf, was Sie mit einem vorhandenen Wert tun, anstatt einen neuen Wert zu erstellen.

4
Caleb

Bedenken Sie

(some_object[index])->some_other_object[more] += 5

D0 du willst wirklich schreiben

(some_object[index])->some_other_object[more] = (some_object[index])->some_other_object[more] + 5
1
S.Lott

Sag es einmal und nur einmal: in x = x + 1, Ich sage zweimal 'x'.

Aber schreibe niemals 'a = b + = 1', sonst müssen wir 10 Kätzchen, 27 Mäuse, einen Hund und einen Hamster töten.


Sie sollten den Wert einer Variablen niemals ändern, da dies den Nachweis der Richtigkeit des Codes erleichtert - siehe Funktionsprogrammierung. Wenn Sie dies jedoch tun, ist es besser, die Dinge nicht nur einmal zu sagen.

1
ctrl-alt-delor

Die anderen Antworten zielen auf die häufigsten Fälle ab, aber es gibt noch einen anderen Grund: In einigen Programmiersprachen kann es überladen sein; z.B. Scala .


Klein Scala Lektion:

var j = 5 #Creates a variable
j += 4    #Compiles

val i = 5 #Creates a constant
i += 4    #Doesn’t compile

Wenn eine Klasse nur den Operator + Definiert, ist x+=y In der Tat eine Verknüpfung von x=x+y.

Wenn eine Klasse += Überlädt, sind dies jedoch nicht:

var a = ""
a += "This works. a now points to a new String."

val b = ""
b += "This doesn’t compile, as b cannot be reassigned."

val c = StringBuffer() #implements +=
c += "This works, as StringBuffer implements “+=(c: String)”."

Außerdem sind die Operatoren + Und += Zwei separate Operatoren (und nicht nur diese: + a, ++ a, a ++, a + b a + = b sind ebenfalls unterschiedliche Operatoren); In Sprachen, in denen eine Überladung des Bedieners verfügbar ist, kann dies zu interessanten Situationen führen. Wie oben beschrieben - Wenn Sie den Operator + Überladen, um das Hinzufügen durchzuführen, müssen Sie berücksichtigen, dass += Ebenfalls überladen werden muss.

1
flying sheep