Verhalten von Inkrement- und Dekrementoperatoren in Python

797

Ich stelle fest, dass ein Vorinkrementierungs- / Dekrementierungsoperator auf eine Variable (wie ++count) angewendet werden kann . Es wird kompiliert, ändert aber den Wert der Variablen nicht!

Wie verhalten sich die Operatoren vor dem Inkrementieren / Dekrementieren (++ / -) in Python?

Warum weicht Python vom Verhalten dieser Operatoren in C / C ++ ab?

Ashwin Nanjappa
quelle
19
Python ist nicht C oder C ++. Bei der Gestaltung der Sprache wurden verschiedene Designentscheidungen getroffen. Insbesondere definiert Python absichtlich keine Zuweisungsoperatoren, die in einem beliebigen Ausdruck verwendet werden können. Vielmehr gibt es Zuweisungsanweisungen und erweiterte Zuweisungsanweisungen. Siehe Referenz unten.
Ned Deily
8
Was hat Sie zu Python ++und --Operatoren gebracht?
u0b34a0f6ae
29
Kaizer: Ich komme aus C / C ++ und schreibe ++ count und es wird in Python kompiliert. Also dachte ich, die Sprache hat die Operatoren.
Ashwin Nanjappa
3
@ Fox Sie gehen von einem Planungs- und Organisationsebene aus, das nicht erkennbar ist
Basic
4
@mehaase ++ und - existieren in c nicht "als syntaktischer Zucker für Zeigerarithmetik", sie existieren, weil viele Prozessoren automatische Inkrementierungs- und Dekrementierungs-Speicherzugriffsmechanismen (im Allgemeinen Zeigerindizierung, Stapelindizierung) als Teil ihrer nativen Anweisung haben einstellen. Zum Beispiel in 6809 Assembler: sta x++... speichert der resultierende atomare Befehl den aAkkumulator, auf den er xzeigt, und erhöht sich dann xum die Größe des Akkumulators. Dies geschieht, weil es schneller als Zeigerarithmetik ist, weil es sehr häufig ist und weil es leicht zu verstehen ist. Sowohl vor als auch nach.
Fyngyrz

Antworten:

1059

++ist kein Operator. Es sind zwei +Operatoren. Der +Operator ist der Identitätsoperator , der nichts tut. (Klarstellung: Die +und -unäre Operatoren arbeiten nur mit Zahlen, aber ich gehe davon aus, dass Sie nicht erwarten würden, dass ein hypothetischer ++Operator mit Zeichenfolgen arbeitet.)

++count

Analysiert als

+(+count)

Was übersetzt bedeutet

count

Sie müssen den etwas längeren +=Operator verwenden, um das zu tun, was Sie tun möchten:

count += 1

Ich vermute, dass die ++und --Operatoren aus Gründen der Konsistenz und Einfachheit weggelassen wurden. Ich kenne das genaue Argument, das Guido van Rossum für die Entscheidung vorgebracht hat, nicht, aber ich kann mir einige Argumente vorstellen:

  • Einfacheres Parsen. Technisch Parsing ++countist nicht eindeutig, wie es sein könnte +, +, count(zwei einstellige +Operatoren) genauso einfach wie es sein könnte ++, count(ein einstelliger ++Operator). Es ist keine signifikante syntaktische Mehrdeutigkeit, aber es existiert.
  • Einfachere Sprache. ++ist nichts weiter als ein Synonym für += 1. Es war eine Kurzform, die erfunden wurde, weil C-Compiler dumm waren und nicht wussten, wie sie a += 1die incAnweisungen der meisten Computer optimieren sollten . In der heutigen Zeit der Optimierung von Compilern und Bytecode-interpretierten Sprachen ist das Hinzufügen von Operatoren zu einer Sprache, mit der Programmierer ihren Code optimieren können, normalerweise verpönt, insbesondere in einer Sprache wie Python, die konsistent und lesbar ist.
  • Verwirrende Nebenwirkungen. Ein häufiger Anfängerfehler in Sprachen mit ++Operatoren besteht darin, die Unterschiede (sowohl in Bezug auf die Priorität als auch in Bezug auf den Rückgabewert) zwischen den Operatoren vor und nach dem Inkrementieren / Dekrementieren zu verwechseln, und Python eliminiert gerne die Sprache "gotcha" -s. Der Rang Fragen von Pre- / Post-Inkrement in C sind ziemlich behaart, und unglaublich einfach zu vermasseln.
Chris Lutz
quelle
13
"Der Operator + ist der Operator" Identität ", der nichts tut." Nur für numerische Typen; Bei anderen Typen handelt es sich standardmäßig um einen Fehler.
Newacct
45
Beachten Sie außerdem, dass in Python + = und Freunde keine Operatoren sind, die in Ausdrücken verwendet werden können. In Python werden sie vielmehr als Teil einer "erweiterten Zuweisungsanweisung" definiert. Dies steht im Einklang mit der Entscheidung des Sprachdesigns in Python, die Zuweisung ("=") als Operator innerhalb beliebiger Ausdrücke nicht zuzulassen, anders als dies in C möglich ist. Siehe docs.python.org/reference/…
Ned Deily
15
Der unäre +Operator hat eine Verwendung. Bei dezimalen.Dezimalobjekten wird auf die aktuelle Genauigkeit gerundet.
u0b34a0f6ae
21
Ich wette auf die Vereinfachung des Parsers. Beachten Sie ein Element in PEP 3099 , "Dinge, die sich in Python 3000 nicht ändern": "Der Parser wird nicht komplexer als LL (1). Einfach ist besser als komplex. Diese Idee erstreckt sich auf den Parser. Einschränkung der Python-Grammatik auf Ein LL (1) -Parser ist ein Segen, kein Fluch. Er legt uns Handschellen an, die uns daran hindern, über Bord zu gehen und funkige Grammatikregeln zu erhalten, wie einige andere dynamische Sprachen, die unbenannt bleiben, wie Perl. " Ich sehe nicht, wie man eindeutig + +und ++ohne LL zu brechen (1).
Mike DeSimone
7
Es ist nicht richtig zu sagen, dass dies ++nichts anderes als ein Synonym für ist += 1. Es gibt Pre-Inkrement- und Post-Inkrement-Varianten von ++, daher ist dies eindeutig nicht dasselbe. Ich stimme jedoch dem Rest Ihrer Punkte zu.
PhilHibbs
384

Wenn Sie inkrementieren oder dekrementieren möchten, möchten Sie dies normalerweise für eine Ganzzahl tun. Wie so:

b++

In Python sind Ganzzahlen jedoch unveränderlich . Das heißt, Sie können sie nicht ändern. Dies liegt daran, dass die ganzzahligen Objekte unter mehreren Namen verwendet werden können. Versuche dies:

>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True

a und b oben sind eigentlich das gleiche Objekt. Wenn Sie a erhöhen, erhöhen Sie auch b. Das willst du nicht. Sie müssen also neu zuweisen. So was:

b = b + 1

Oder einfacher:

b += 1

Was neu zuweisen wird bzu b+1. Dies ist kein Inkrementierungsoperator, da er nicht inkrementiert b, sondern neu zugewiesen wird.

Kurz gesagt: Python verhält sich hier anders, weil es nicht C ist und kein Wrapper auf niedriger Ebene um Maschinencode, sondern eine dynamische Sprache auf hoher Ebene, in der Inkremente keinen Sinn ergeben und auch nicht so notwendig sind wie in C. , wo Sie sie zum Beispiel jedes Mal verwenden, wenn Sie eine Schleife haben.

Lennart Regebro
quelle
75
Dieses Beispiel ist falsch (und Sie verwechseln wahrscheinlich Unveränderlichkeit mit Identität) - sie haben dieselbe ID aufgrund einer VM-Optimierung, die dieselben Objekte für Zahlen bis 255 verwendet (oder so ähnlich). ZB (größere Zahlen): >>> a = 1231231231231 >>> b = 1231231231231 >>> ID (a), ID (b) (32171144, 32171168)
ionelmc
56
Der Unveränderlichkeitsanspruch ist falsch. Konzeptionell i++würde bedeuten, i + 1der Variablen zuzuweisen i. i = 5; i++Mittel zuzuordnen , 6um idie nicht zu ändern intObjekt , auf das durch i. Das heißt, es bedeutet nicht, den Wert von5 zu erhöhen !
Mechanische Schnecke
3
@ Mechanische Schnecke: In diesem Fall wären es überhaupt keine Inkrementoperatoren. Und dann ist der Operator + = klarer, expliziter, flexibler und macht sowieso dasselbe.
Lennart Regebro
7
@LennartRegebro: In C ++ und Java werden i++nur l-Werte verarbeitet. Wenn beabsichtigt wäre, das Objekt, auf das verwiesen wird i, zu erhöhen, wäre diese Einschränkung nicht erforderlich.
Mechanische Schnecke
4
Ich finde diese Antwort ziemlich verwirrend. Warum nehmen Sie an, dass ++ etwas anderes als eine Abkürzung für + = 1 bedeuten würde? Genau das bedeutet es in C (vorausgesetzt, der Rückgabewert wird nicht verwendet). Sie scheinen eine andere Bedeutung aus der Luft gezogen zu haben.
Don Hatch
52

Während die anderen Antworten insofern richtig sind, als sie zeigen, was ein bloßes +tut (nämlich die Zahl so lassen, wie sie ist, wenn es eine ist), sind sie insofern unvollständig, als sie nicht erklären, was passiert.

Um genau zu sein, +xbewertet zu x.__pos__()und ++xzu x.__pos__().__pos__().

Ich könnte mir eine SEHR seltsame Klassenstruktur vorstellen (Kinder, mach das nicht zu Hause!) Wie diese:

class ValueKeeper(object):
    def __init__(self, value): self.value = value
    def __str__(self): return str(self.value)

class A(ValueKeeper):
    def __pos__(self):
        print 'called A.__pos__'
        return B(self.value - 3)

class B(ValueKeeper):
    def __pos__(self):
        print 'called B.__pos__'
        return A(self.value + 19)

x = A(430)
print x, type(x)
print +x, type(+x)
print ++x, type(++x)
print +++x, type(+++x)
glglgl
quelle
13

Python verfügt nicht über diese Operatoren, aber wenn Sie sie wirklich benötigen, können Sie eine Funktion mit derselben Funktionalität schreiben.

def PreIncrement(name, local={}):
    #Equivalent to ++name
    if name in local:
        local[name]+=1
        return local[name]
    globals()[name]+=1
    return globals()[name]

def PostIncrement(name, local={}):
    #Equivalent to name++
    if name in local:
        local[name]+=1
        return local[name]-1
    globals()[name]+=1
    return globals()[name]-1

Verwendungszweck:

x = 1
y = PreIncrement('x') #y and x are both 2
a = 1
b = PostIncrement('a') #b is 1 and a is 2

Innerhalb einer Funktion müssen Sie local () als zweites Argument hinzufügen, wenn Sie die lokale Variable ändern möchten. Andernfalls wird versucht, die globale Variable zu ändern.

x = 1
def test():
    x = 10
    y = PreIncrement('x') #y will be 2, local x will be still 10 and global x will be changed to 2
    z = PreIncrement('x', locals()) #z will be 11, local x will be 11 and global x will be unaltered
test()

Auch mit diesen Funktionen können Sie:

x = 1
print(PreIncrement('x'))   #print(x+=1) is illegal!

Meiner Meinung nach ist der folgende Ansatz jedoch viel klarer:

x = 1
x+=1
print(x)

Dekrementierungsoperatoren:

def PreDecrement(name, local={}):
    #Equivalent to --name
    if name in local:
        local[name]-=1
        return local[name]
    globals()[name]-=1
    return globals()[name]

def PostDecrement(name, local={}):
    #Equivalent to name--
    if name in local:
        local[name]-=1
        return local[name]+1
    globals()[name]-=1
    return globals()[name]+1

Ich habe diese Funktionen in meinem Modul verwendet, um Javascript in Python zu übersetzen.

Piotr Dabkowski
quelle
Hinweis: Diese Hilfsmethoden funktionieren zwar hervorragend, funktionieren jedoch nicht, wenn Ihre Einheimischen im Stapelrahmen für Klassenfunktionen vorhanden sind. Das heißt, das Aufrufen aus einer Klassenmethode def funktioniert nicht. Das Diktat 'local ()' ist eine Momentaufnahme und aktualisiert den Stapelrahmen nicht.
Adam
11

In Python wird im Gegensatz zu Sprachen wie Common Lisp, Scheme oder Ruby eine Unterscheidung zwischen Ausdrücken und Anweisungen streng erzwungen.

Wikipedia

Wenn Sie also solche Operatoren einführen, können Sie die Aufteilung von Ausdruck und Anweisung aufheben.

Aus dem gleichen Grund kann man nicht schreiben

if x = 0:
  y = 1

wie Sie können in einigen anderen Sprachen, in denen eine solche Unterscheidung nicht erhalten bleibt.

Vitalii Fedorenko
quelle
Interessanterweise wird diese Einschränkung in der kommenden Version Python 3.8 mit der neuen Syntax für Zuweisungsausdrücke (PEP-572 python.org/dev/peps/pep-0572 ) aufgehoben . Wir können zum if (n := len(a)) > 10: y = n + 1Beispiel schreiben . Beachten Sie, dass die Unterscheidung aufgrund der Einführung eines neuen Operators für diesen Zweck klar ist ( :=)
Zertrin
8

TL; DR

Python hat keine unären Inkrementierungs- / Dekrementierungsoperatoren ( --/ ++). Verwenden Sie stattdessen, um einen Wert zu erhöhen

a += 1

Mehr Details und Fallstricke

Aber sei hier vorsichtig. Wenn Sie aus C kommen, ist auch dies in Python anders. Python hat keine "Variablen" in dem Sinne wie C, stattdessen verwendet Python Namen und Objekte und in Python intsind sie unveränderlich.

Nehmen wir an, Sie tun es

a = 1

In Python bedeutet dies: Erstellen Sie ein Objekt vom Typ intmit Wert 1und binden Sie den Namen adaran. Das Objekt ist eine Instanz intmit Wert 1, und der Name a verweist darauf. Der Name aund das Objekt, auf das es sich bezieht, sind unterschiedlich.

Nehmen wir jetzt an, Sie tun es

a += 1

Da ints unveränderlich sind, geschieht hier Folgendes:

  1. Suchen Sie nach dem Objekt, aauf das verwiesen wird (es ist eine intmit ID 0x559239eeb380).
  2. den Wert des Objekts nachschlagen 0x559239eeb380(es ist 1)
  3. addiere 1 zu diesem Wert (1 + 1 = 2)
  4. Erstellen Sie ein neues int Objekt mit Wert 2(es hat Objekt-ID 0x559239eeb3a0)
  5. Binden Sie den Namen erneut aan dieses neue Objekt
  6. aBezieht sich jetzt auf das Objekt 0x559239eeb3a0und das ursprüngliche Objekt ( 0x559239eeb380) wird nicht mehr mit dem Namen bezeichnet a. Wenn keine anderen Namen auf das ursprüngliche Objekt verweisen, wird der Müll später gesammelt.

Probieren Sie es selbst aus:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
RBF06
quelle
6

Ja, ich habe auch ++ und - Funktionalität vermisst. Ein paar Millionen Zeilen C-Code haben diese Art des Denkens in meinem alten Kopf verankert, und anstatt dagegen anzukämpfen ... Hier ist eine Klasse, die ich zusammengeschustert habe und die Folgendes implementiert:

pre- and post-increment, pre- and post-decrement, addition,
subtraction, multiplication, division, results assignable
as integer, printable, settable.

Hier ist es:

class counter(object):
    def __init__(self,v=0):
        self.set(v)

    def preinc(self):
        self.v += 1
        return self.v
    def predec(self):
        self.v -= 1
        return self.v

    def postinc(self):
        self.v += 1
        return self.v - 1
    def postdec(self):
        self.v -= 1
        return self.v + 1

    def __add__(self,addend):
        return self.v + addend
    def __sub__(self,subtrahend):
        return self.v - subtrahend
    def __mul__(self,multiplier):
        return self.v * multiplier
    def __div__(self,divisor):
        return self.v / divisor

    def __getitem__(self):
        return self.v

    def __str__(self):
        return str(self.v)

    def set(self,v):
        if type(v) != int:
            v = 0
        self.v = v

Sie könnten es so verwenden:

c = counter()                          # defaults to zero
for listItem in myList:                # imaginary task
     doSomething(c.postinc(),listItem) # passes c, but becomes c+1

... bereits mit c, könnten Sie dies tun ...

c.set(11)
while c.predec() > 0:
    print c

....oder nur...

d = counter(11)
while d.predec() > 0:
    print d

... und für die (Neu-) Zuordnung in eine Ganzzahl ...

c = counter(100)
d = c + 223 # assignment as integer
c = c + 223 # re-assignment as integer
print type(c),c # <type 'int'> 323

... während dies c als Typzähler beibehält:

c = counter(100)
c.set(c + 223)
print type(c),c # <class '__main__.counter'> 323

BEARBEITEN:

Und dann gibt es dieses unerwartete (und völlig unerwünschte) Verhalten .

c = counter(42)
s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exception
print s

... weil in diesem Tupel getitem () nicht verwendet wird, sondern ein Verweis auf das Objekt an die Formatierungsfunktion übergeben wird. Seufzer. Damit:

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.v) # and getting 42.
print s

... oder wörtlicher und expliziter, was wir eigentlich wollten, obwohl dies in tatsächlicher Form durch die Ausführlichkeit entgegengesetzt wird ( c.vstattdessen verwenden) ...

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
Fyngyrz
quelle
2

In Python gibt es keine Post / Pre-Inkrement- / Dekrement-Operatoren wie in Sprachen wie C.

Wir können sehen ++oder --als mehrere Zeichen multiplizieren, wie wir es in Mathe (-1) * (-1) = (+1) tun.

Z.B

---count

Analysiert als

-(-(-count)))

Was übersetzt bedeutet

-(+count)

Denn Multiplikation von -Vorzeichen mit -Vorzeichen ist+

Und schlussendlich,

-count
Anuj
quelle
1
Was sagt dies, was andere Antworten nicht sagen?
Daniel B.
@ DanielB. Andere Antworten haben nicht gesagt, was intern passiert. Und sie haben auch nicht gesagt, was passieren wird, wenn Sie schreiben -----count.
Anuj
Die erste akzeptierte Antwort tut es. ...
Daniel B.
2
Es wird nicht erwähnt, dass eine Multiplikation durchgeführt wird, daher dachte ich, ein Konsice und eine auf den Punkt gebrachte Antwort wären für Mitbenutzer nützlich. Nichts für ungut, wenn Sie das verstanden haben. Lernen ist wichtiger als die Quelle, aus der Sie lernen.
Anuj
0

In Python 3.8+ können Sie Folgendes tun:

(a:=a+1) #same as a++

Damit kann man viel nachdenken.

>>> a = 0
>>> while (a:=a+1) < 5:
    print(a)


1
2
3
4

Oder wenn Sie etwas mit einer ausgefeilteren Syntax schreiben möchten (das Ziel ist nicht die Optimierung):

>>> del a
>>> while (a := (a if 'a' in locals() else 0) + 1) < 5:
    print(a)


1
2
3
4

Es gibt gut 0 zurück, wenn ein nicht fehlerfrei existiert, und setzt es dann auf 1

Henry
quelle