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.