Comportamento degli operatori di incremento e decremento in Python


798

Noto che un operatore di pre-incremento / decremento può essere applicato su una variabile (come ++count). Si compila, ma in realtà non cambia il valore della variabile!

Qual è il comportamento degli operatori di pre-incremento / decremento (++ / -) in Python?

Perché Python si discosta dal comportamento di questi operatori visto in C / C ++?


19
Python non è C o C ++. Diverse decisioni di progettazione sono andate nel rendere il linguaggio. In particolare, Python non definisce deliberatamente operatori di assegnazione che possono essere utilizzati in un'espressione arbitraria; piuttosto, ci sono dichiarazioni di assegnazione e dichiarazioni di assegnazione aumentate. Vedi riferimento sotto.
— Ned Deily,

8
Cosa ti ha fatto pensare che Python avesse ++e gli --operatori?
— u0b34a0f6ae,

29
Kaizer: Proveniente da C / C ++, scrivo ++ count e si compila in Python. Quindi, ho pensato che la lingua avesse gli operatori.
— Ashwin Nanjappa,

3
@Fox Stai assumendo un livello di pianificazione e organizzazione non in evidenza
— Basic

4
@mehaase ++ e - non esistono in c "come zucchero sintattico per l'aritmetica dei puntatori", esistono perché molti processori hanno meccanismi automatici di incremento e decremento dell'accesso alla memoria (in generale indicizzazione dei puntatori, indicizzazione dello stack) come parte delle loro istruzioni native impostato. Ad esempio, nell'assemblatore 6809: sta x++... l'istruzione atomica risultante memorizza l' aaccumulatore dove xsta puntando, quindi aumenta xdella dimensione dell'accumulatore. Questo perché è più veloce dell'aritmetica del puntatore, perché è molto comune e perché è facile da capire. Sia pre che post.
— fyngyrz,

Risposte:


1059

++non è un operatore. Sono due +operatori. L' +operatore è l' operatore di identità , che non fa nulla. (Precisazione: l' +e -operatori unari solo lavoro sui numeri, ma presumo che non ci si aspetterebbe un ipotetico ++. All'operatore di lavorare sulle stringhe)

++count

Analizza come

+(+count)

Che si traduce in

count

Devi usare l' +=operatore leggermente più lungo per fare quello che vuoi fare:

count += 1

Sospetto che gli operatori ++e siano --stati esclusi per coerenza e semplicità. Non conosco l'argomento esatto fornito da Guido van Rossum per la decisione, ma posso immaginare alcuni argomenti:

  • Analisi più semplice. Tecnicamente, l'analisi ++countè ambiguo, come potrebbe essere +, +, count(due unari +operatori) facilmente come potrebbe essere ++, count(uno unario ++operatore). Non è una significativa ambiguità sintattica, ma esiste.
  • Linguaggio più semplice. ++non è altro che un sinonimo di += 1. Era una scorciatoia inventata perché i compilatori C erano stupidi e non sapevano come ottimizzare a += 1le incistruzioni della maggior parte dei computer. In questo giorno di ottimizzazione di compilatori e linguaggi interpretati con bytecode, l'aggiunta di operatori a una lingua per consentire ai programmatori di ottimizzare il loro codice è generalmente disapprovata, specialmente in un linguaggio come Python progettato per essere coerente e leggibile.
  • Effetti collaterali confusi. Un errore newbie comune nelle lingue con gli ++operatori è quello di confondere le differenze (sia in precedenza che in valore di ritorno) tra gli operatori pre e post-incremento / decremento, e Python ama eliminare i "gotcha" del linguaggio. I problemi di precedenza del pre / post-incremento in C sono piuttosto pelosi e incredibilmente facili da rovinare.

13
"L'operatore + è l'operatore" identità ", che non fa nulla." Solo per tipi numerici; per altro tipo è un errore di default.
— Newacct,

45
Inoltre, tieni presente che, in Python, + = e gli amici non sono operatori che possono essere utilizzati nelle espressioni. Piuttosto, in Python sono definiti come parte di una "istruzione di assegnazione aumentata". Ciò è coerente con la decisione di progettazione del linguaggio in Python di non consentire l'assegnazione ("=") come operatore all'interno di espressioni arbitrarie, a differenza di ciò che si può fare in C. Vedi docs.python.org/reference/…
— Ned Deily,

15
L' +operatore unario ha un uso. Per gli oggetti decimali, decimali, arrotondano alla precisione corrente.
— u0b34a0f6ae,

21
Scommetto sulla semplificazione del parser. Nota un elemento in PEP 3099 , "Cose che non cambieranno in Python 3000": "Il parser non sarà più complesso di LL (1). Semplice è meglio che complesso. Questa idea si estende al parser. Limitando la grammatica di Python a un parser LL (1) è una benedizione, non una maledizione. Ci mette in manette che ci impediscono di esagerare e di finire con regole grammaticali funky come alcune altre lingue dinamiche che rimarranno senza nome, come Perl. " Non vedo come disambiguare + +e ++senza rompere LL (1).
— Mike DeSimone,

7
Non è corretto dire che ++non è altro che un sinonimo di += 1. Esistono varianti pre-incremento e post-incremento di ++, quindi chiaramente non è la stessa cosa. Sono d'accordo con il resto dei tuoi punti, però.
— PhilHibbs,

384

Quando si desidera aumentare o diminuire, in genere si desidera farlo su un numero intero. Così:

b++

Ma in Python, i numeri interi sono immutabili . Cioè non puoi cambiarli. Questo perché gli oggetti interi possono essere utilizzati con diversi nomi. Prova questo:

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

aeb sopra sono in realtà lo stesso oggetto. Se si incrementa a, si aumenterà anche b. Non è quello che vuoi. Quindi devi riassegnare. Come questo:

b = b + 1

O più semplice:

b += 1

Che si riassegnerà ba b+1. Questo non è un operatore di incremento, perché non incrementa b, lo riassegna.

In breve: Python si comporta diversamente qui, perché non è C, e non è un wrapper di basso livello attorno al codice macchina, ma un linguaggio dinamico di alto livello, in cui gli incrementi non hanno senso e inoltre non sono necessari come in C , dove li usi ogni volta che hai un loop, ad esempio.


75
Quell'esempio è sbagliato (e probabilmente stai confondendo l'immutabilità con l'identità): hanno lo stesso ID a causa di un'ottimizzazione VM che utilizza gli stessi oggetti per i numeri fino a 255 (o qualcosa del genere). Ad esempio (numeri più grandi): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
— ionelmc,

56
L'affermazione sull'immutabilità è falsa. Concettualmente, i++significherebbe assegnare i + 1alla variabile i . i = 5; i++significa assegnare 6a i, non modificare l' intoggetto a cui punta i. Cioè, non significa aumentare il valore di5 !
— Lumaca meccanica

3
@ Lumaca meccanica: nel qual caso non si tratterebbe di incrementare gli operatori. E poi l'operatore + = è più chiaro, più esplicito, più flessibile e fa comunque la stessa cosa.
— Lennart Regebro,

7
@LennartRegebro: in C ++ e Java, i++funziona solo su valori. Se si intendesse incrementare l'oggetto indicato da i, questa restrizione non sarebbe necessaria.
— Lumaca meccanica

4
Trovo questa risposta piuttosto sconcertante. Perché stai supponendo che ++ significhi qualcosa di diverso da una scorciatoia per + = 1? Questo è esattamente ciò che significa in C (supponendo che il valore restituito non sia utilizzato). Sembra che tu abbia tirato fuori un altro significato dall'aria.
— Don Hatch,

52

Mentre le altre risposte sono corrette nella misura in cui mostrano cosa +fa di solito un mero (vale a dire, lasciare il numero così com'è, se è uno), sono incomplete in quanto non spiegano cosa succede.

Per l'esattezza, +xvaluta da x.__pos__()e ++xverso x.__pos__().__pos__().

Potrei immaginare una struttura di classe MOLTO strana (Bambini, non farlo a casa!) In questo modo:

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)

13

Python non ha questi operatori, ma se ne hai davvero bisogno puoi scrivere una funzione con la stessa funzionalità.

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

Uso:

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

All'interno di una funzione devi aggiungere locals () come secondo argomento se vuoi cambiare la variabile locale, altrimenti proverà a cambiare globale.

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()

Inoltre con queste funzioni puoi fare:

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

Ma secondo me il seguente approccio è molto più chiaro:

x = 1
x+=1
print(x)

Decrementa operatori:

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

Ho usato queste funzioni nel mio modulo traducendo javascript in python.


Nota: benché ottimi, questi metodi di supporto non funzioneranno se i tuoi locali esistono nel frame dello stack delle funzioni di classe. vale a dire - chiamarli dall'interno di un metodo di classe def non funzionerà - il dict 'locals ()' è un'istantanea e non aggiorna il frame dello stack.
— Adam,

11

In Python, viene fatta una rigida distinzione tra espressioni e affermazioni, in contrasto con linguaggi come Common Lisp, Scheme o Ruby.

Wikipedia

Quindi, introducendo tali operatori, si spezzerebbe la divisione espressione / istruzione.

Per lo stesso motivo per cui non puoi scrivere

if x = 0:
  y = 1

come puoi in alcune altre lingue in cui tale distinzione non viene preservata.


È interessante notare che questa restrizione sarà revocata nella prossima versione di Python 3.8 con la nuova sintassi per le espressioni di assegnazione (PEP-572 python.org/dev/peps/pep-0572 ). Saremo in grado di scrivere if (n := len(a)) > 10: y = n + 1per esempio. Si noti che la distinzione è chiara a causa dell'introduzione di un nuovo operatore a tale scopo ( :=)
— Zertrin,

8

TL; DR

Python non ha operatori di incremento / decremento unari ( --/ ++). Invece, per incrementare un valore, usare

a += 1

Più dettagli e gotchas

Ma fai attenzione qui. Se vieni da C, anche questo è diverso in Python. Python non ha "variabili" nel senso che fa C, invece python usa nomi e oggetti , e in python intsono immutabili.

quindi diciamo che lo fai

a = 1

Ciò che significa in Python è: creare un oggetto di tipo intcon valore 1e associare il nome aad esso. L' oggetto è un'istanza intcon valore 1e il nome si a riferisce ad esso. Il nome ae l'oggetto a cui si riferisce sono distinti.

Ora diciamo che lo fai

a += 1

Poiché ints sono immutabili, ciò che accade qui è il seguente:

  1. cerca l'oggetto a cui si ariferisce (è un intcon ID 0x559239eeb380)
  2. cerca il valore dell'oggetto 0x559239eeb380(lo è 1)
  3. aggiungi 1 a quel valore (1 + 1 = 2)
  4. crea un nuovo int oggetto con valore 2(ha un ID oggetto 0x559239eeb3a0)
  5. ricollegare il nome aa questo nuovo oggetto
  6. Ora si ariferisce all'oggetto 0x559239eeb3a0e l'oggetto originale ( 0x559239eeb380) non è più indicato dal nome a. Se non ci sono altri nomi che fanno riferimento all'oggetto originale, sarà spazzato via dopo.

Provalo tu stesso:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))

6

Sì, mi sono perso anche ++ e - funzionalità. Alcuni milioni di righe di codice c hanno inciso quel tipo di pensiero nella mia vecchia testa e invece di combatterlo ... Ecco una classe che ho messo insieme che implementa:

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

Ecco qui:

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

Potresti usarlo in questo modo:

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

... già avendo c, potresti farlo ...

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

.... o solo ...

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

... e per (ri) assegnazione in numero intero ...

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

... mentre ciò manterrà c come contatore dei tipi:

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

MODIFICARE:

E poi c'è un po 'di comportamento inaspettato (e completamente indesiderato) ,

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

... perché all'interno di quella tupla, getitem () non è quello usato, invece un riferimento all'oggetto viene passato alla funzione di formattazione. Sospiro. Così:

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

... o, più verbalmente, ed esplicitamente ciò che volevamo realmente accadere, sebbene controindicato in forma reale dalla verbosità (usare c.vinvece) ...

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

2

Non ci sono operatori di post / pre incremento / decremento in Python come in linguaggi come C.

Possiamo vedere ++o --come moltiplicare più segni, come facciamo in matematica (-1) * (-1) = (+1).

Per esempio

---count

Analizza come

-(-(-count)))

Che si traduce in

-(+count)

Perché, la moltiplicazione del -segno con il -segno è+

E infine,

-count

1
Cosa dice questo che le altre risposte non lo fanno?
— Daniel B.

@DanielB. Altre risposte non hanno detto cosa succede internamente. E nessuno dei due ha detto cosa accadrà quando scriverai -----count.
— Anuj,

La prima risposta accettata fa. ...
— Daniel B.

2
Non vi è alcuna menzione del fatto che la moltiplicazione è in corso, quindi ho pensato che una consulenza e la risposta al punto sarebbero state utili per gli altri utenti. Senza offesa se lo capissi. L'apprendimento è più importante della fonte da cui apprendi.
— Anuj,

0

In Python 3.8+ puoi fare:

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

Puoi fare molte cose con questo.

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


1
2
3
4

O se vuoi scrivere qualcosa con una sintassi più sofisticata (l'obiettivo non è l'ottimizzazione):

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


1
2
3
4

Restituisce bene 0 se un dos non esiste senza errori e quindi lo imposterà su 1

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.