Comportement des opérateurs d'incrémentation et de décrémentation en Python

798

Je remarque qu'un opérateur de pré-incrémentation / décrémentation peut être appliqué sur une variable (comme ++count). Il compile, mais il ne change pas réellement la valeur de la variable!

Quel est le comportement des opérateurs de pré-incrémentation / décrémentation (++ / -) en Python?

Pourquoi Python s'écarte du comportement de ces opérateurs vu en C / C ++?

Ashwin Nanjappa
la source
19
Python n'est pas C ou C ++. Différentes décisions de conception ont été prises pour créer le langage. En particulier, Python ne définit pas délibérément des opérateurs d'affectation pouvant être utilisés dans une expression arbitraire; il existe plutôt des instructions d'affectation et des instructions d'affectation augmentées. Voir référence ci-dessous.
Ned Deily
8
Qu'est - ce qui vous pensez python avait ++et les --opérateurs?
u0b34a0f6ae
29
Kaizer: Venant de C / C ++, j'écris ++ count et il compile en Python. Donc, je pensais que la langue avait les opérateurs.
Ashwin Nanjappa
3
@Fox Vous supposez un niveau de planification et d'organisation pas en évidence
Basique
4
@mehaase ++ et - n'existent pas en c "comme sucre syntaxique pour l'arithmétique des pointeurs", ils existent parce que de nombreux processeurs ont des mécanismes d'accès automatique à la mémoire incrémentés et décrémentés (en général indexation de pointeurs, indexation de pile) dans le cadre de leur instruction native ensemble. Par exemple, dans l'assembleur 6809: sta x++... l'instruction atomique qui en résulte stocke l' aaccumulateur où il xpointe, puis incrémente xde la taille de l'accumulateur. Cela est dû au fait qu'il est plus rapide que l'arithmétique des pointeurs, parce qu'il est très courant et qu'il est facile à comprendre. Pré et post.
fyngyrz

Réponses:

1060

++n'est pas un opérateur. Il s'agit de deux +opérateurs. L' +opérateur est l' opérateur d' identité , qui ne fait rien. (Clarification: les opérateurs unary +et -ne fonctionnent que sur les nombres, mais je suppose que vous ne vous attendriez pas à ce qu'un ++opérateur hypothétique travaille sur les chaînes.)

++count

Analyse comme

+(+count)

Ce qui se traduit par

count

Vous devez utiliser l' +=opérateur légèrement plus long pour faire ce que vous voulez faire:

count += 1

Je soupçonne que les opérateurs ++et --ont été omis pour des raisons de cohérence et de simplicité. Je ne connais pas l'argument exact que Guido van Rossum a avancé pour la décision, mais je peux imaginer quelques arguments:

  • Analyse plus simple. Techniquement, l' analyse syntaxique ++countest ambiguë, car elle pourrait être +, +, count(deux unaire +opérateurs) tout aussi facilement qu'il pourrait l' être ++, count(un unaire ++opérateur). Ce n'est pas une ambiguïté syntaxique significative, mais elle existe.
  • Langage plus simple. ++n'est rien de plus qu'un synonyme de += 1. C'était un raccourci inventé parce que les compilateurs C étaient stupides et ne savaient pas comment optimiser a += 1les incinstructions de la plupart des ordinateurs. En cette journée d'optimisation des compilateurs et des langages interprétés par bytecode, l'ajout d'opérateurs à un langage pour permettre aux programmeurs d'optimiser leur code est généralement mal vu, en particulier dans un langage comme Python conçu pour être cohérent et lisible.
  • Effets secondaires déroutants. Une erreur courante pour les débutants dans les langues avec ++opérateurs consiste à mélanger les différences (à la fois en priorité et en valeur de retour) entre les opérateurs pré et post-incrémentation / décrémentation, et Python aime éliminer les "gotcha" -s de langue. Les problèmes de priorité de pré / post-incrémentation en C sont assez velus et incroyablement faciles à gâcher.
Chris Lutz
la source
13
"L'opérateur + est l'opérateur" identité ", qui ne fait rien." Uniquement pour les types numériques; pour les autres types, c'est une erreur par défaut.
newacct
45
Sachez également qu'en Python, + = et friends ne sont pas des opérateurs utilisables dans les expressions. Au contraire, en Python, ils sont définis comme faisant partie d'une "instruction d'affectation augmentée". Ceci est cohérent avec la décision de conception du langage en Python de ne pas autoriser l'affectation ("=") en tant qu'opérateur dans des expressions arbitraires, contrairement à ce que l'on peut faire en C. Voir docs.python.org/reference/…
Ned Deily
15
L' +opérateur unaire a une utilité. Pour les objets decimal.Decimal, il arrondit à la précision actuelle.
u0b34a0f6ae
21
Je parie sur la simplification de l'analyseur. Notez un élément dans PEP 3099 , "Choses qui ne changeront pas dans Python 3000": "L'analyseur ne sera pas plus complexe que LL (1). Simple vaut mieux que complexe. Cette idée s'étend à l'analyseur. Restreindre la grammaire de Python à un analyseur LL (1) est une bénédiction, pas une malédiction. Il nous met des menottes qui nous empêchent d'aller par dessus bord et de nous retrouver avec des règles de grammaire funky comme certains autres langages dynamiques qui ne seront pas nommés, comme Perl. " Je ne vois pas comment lever l'ambiguïté + +et ++sans casser LL (1).
Mike DeSimone
7
Il n'est pas correct de dire que ce ++n'est rien de plus qu'un synonyme de += 1. Il existe des variantes pré-incrémentation et post-incrémentation de ++ donc ce n'est clairement pas la même chose. Je suis cependant d'accord avec le reste de vos points.
PhilHibbs
384

Lorsque vous souhaitez incrémenter ou décrémenter, vous voulez généralement le faire sur un entier. Ainsi:

b++

Mais en Python, les entiers sont immuables . C'est que vous ne pouvez pas les changer. En effet, les objets entiers peuvent être utilisés sous plusieurs noms. Essaye ça:

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

a et b ci-dessus sont en fait le même objet. Si vous incrémentiez a, vous incrémenteriez également b. Ce n'est pas ce que tu veux. Vous devez donc réaffecter. Comme ça:

b = b + 1

Ou plus simple:

b += 1

Qui sera réaffecté bà b+1. Ce n'est pas un opérateur d'incrémentation, car il n'incrémente pas b, il le réaffecte.

En bref: Python se comporte différemment ici, car ce n'est pas C et ce n'est pas un wrapper de bas niveau autour du code machine, mais un langage dynamique de haut niveau, où les incréments n'ont pas de sens et ne sont pas aussi nécessaires que dans C , où vous les utilisez chaque fois que vous avez une boucle, par exemple.

Lennart Regebro
la source
75
Cet exemple est erroné (et vous confondez probablement l'immuabilité avec l'identité) - ils ont le même identifiant en raison d'une optimisation vm qui utilise les mêmes objets pour les nombres jusqu'à 255 (ou quelque chose comme ça). Par exemple (plus grands nombres): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc
56
La revendication d'immuabilité est fausse. Conceptuellement, i++cela signifierait attribuer i + 1à la variable i . i = 5; i++signifie assigner 6à i, et non modifier l' intobjet pointé par i. Autrement dit, cela ne signifie pas d' augmenter la valeur de5 !
Escargot mécanique
3
@Mechanical snail: Dans ce cas, ce ne serait pas du tout des opérateurs d'incrémentation. Et puis l'opérateur + = est plus clair, plus explicite, plus flexible et fait quand même la même chose.
Lennart Regebro
7
@LennartRegebro: En C ++ et Java, i++ne fonctionne que sur lvalues. S'il était prévu d'incrémenter l'objet pointé par i, cette restriction serait inutile.
Escargot mécanique
4
Je trouve cette réponse assez déconcertante. Pourquoi supposez-vous que ++ signifierait autre chose qu'un raccourci pour + = 1? C'est précisément ce que cela signifie en C (en supposant que la valeur de retour n'est pas utilisée). Vous semblez avoir tiré un autre sens de l'air.
Don Hatch
52

Alors que les autres réponses sont correctes dans la mesure où elles montrent ce qu'est + habituellement (à savoir, laisser le numéro tel quel, s'il est un), elles sont incomplètes dans la mesure où elles n'expliquent pas ce qui se passe.

Pour être exact, +xévalue vers x.__pos__()et ++xvers x.__pos__().__pos__().

Je pourrais imaginer une structure de classe TRÈS bizarre (les enfants, ne faites pas ça à la maison!) Comme ceci:

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
la source
13

Python n'a pas ces opérateurs, mais si vous en avez vraiment besoin, vous pouvez écrire une fonction ayant les mêmes fonctionnalités.

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

Usage:

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

A l'intérieur d'une fonction, vous devez ajouter locals () comme second argument si vous voulez changer la variable locale, sinon elle essaiera de changer global.

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

Avec ces fonctions, vous pouvez également:

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

Mais à mon avis, l'approche suivante est beaucoup plus claire:

x = 1
x+=1
print(x)

Opérateurs de décrémentation:

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

J'ai utilisé ces fonctions dans mon module traduisant javascript en python.

Piotr Dabkowski
la source
Remarque: bien qu'elles soient excellentes, ces méthodes d'assistance ne fonctionneront pas si vos sections locales existent sur le cadre de pile de fonctions de classe. c'est-à-dire - les appeler à partir d'une méthode de classe def ne fonctionnera pas - le dict 'locals ()' est un instantané et ne met pas à jour le cadre de la pile.
Adam
11

En Python, une distinction entre les expressions et les instructions est rigoureusement appliquée, contrairement aux langages tels que Common Lisp, Scheme ou Ruby.

Wikipédia

Ainsi, en introduisant de tels opérateurs, vous briseriez la séparation expression / instruction.

Pour la même raison, vous ne pouvez pas écrire

if x = 0:
  y = 1

comme vous pouvez dans certaines autres langues où cette distinction n'est pas préservée.

Vitalii Fedorenko
la source
Fait intéressant, cette restriction sera levée dans la prochaine version Python 3.8 avec la nouvelle syntaxe des expressions d'assignation (PEP-572 python.org/dev/peps/pep-0572 ). Nous pourrons écrire if (n := len(a)) > 10: y = n + 1par exemple. Notez que la distinction est claire en raison de l'introduction d'un nouvel opérateur à cet effet ( :=)
Zertrin
8

TL; DR

Python n'a pas d'opérateurs unaires d'incrémentation / décrémentation ( --/ ++). Au lieu de cela, pour incrémenter une valeur, utilisez

a += 1

Plus de détails et gotchas

Mais faites attention ici. Si vous venez de C, même cela est différent en python. Python n'a pas de "variables" dans le sens où C le fait, à la place python utilise des noms et des objets , et en python ints sont immuables.

alors disons que vous le faites

a = 1

En python, cela signifie: créer un objet de type intayant une valeur 1et lui lier le nom a. L' objet est une instance de intvaleur 1et le nom y a fait référence. Le nom aet l'objet auquel il se réfère sont distincts.

Maintenant, disons que oui

a += 1

Puisque les ints sont immuables, ce qui se passe ici est le suivant:

  1. rechercher l'objet qui afait référence (c'est un intavec id 0x559239eeb380)
  2. rechercher la valeur de l'objet 0x559239eeb380(c'est 1)
  3. ajoutez 1 à cette valeur (1 + 1 = 2)
  4. créer un nouvel int objet avec une valeur 2(il a un identifiant d'objet 0x559239eeb3a0)
  5. relier le nom aà ce nouvel objet
  6. Désormais afait référence à l'objet 0x559239eeb3a0et l'objet d'origine ( 0x559239eeb380) n'est plus référencé par le nom a. S'il n'y a aucun autre nom faisant référence à l'objet d'origine, il sera récupéré plus tard.

Essayez vous-même:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
RBF06
la source
6

Ouais, j'ai aussi manqué la fonctionnalité ++ et -. Quelques millions de lignes de code c ont ancré ce genre de pensée dans ma vieille tête, et plutôt que de le combattre ... Voici une classe que j'ai bricolé qui implémente:

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

Voici:

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

Vous pouvez l'utiliser comme ceci:

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

... ayant déjà c, vous pouvez le faire ...

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

....ou juste...

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

... et pour la (ré) affectation en entier ...

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

... alors que cela maintiendra c comme compteur de type:

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

ÉDITER:

Et puis il y a ce petit comportement inattendu (et complètement indésirable) ,

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

... parce qu'à l'intérieur de ce tuple, getitem () n'est pas ce qui est utilisé, mais une référence à l'objet est passée à la fonction de formatage. Soupir. Donc:

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

... ou, plus verbalement, et explicitement ce que nous voulions réellement arriver, bien que contre-indiqué sous sa forme réelle par la verbosité (utiliser à la c.vplace) ...

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

Il n'y a pas d'opérateurs post / pre increment / decrement en python comme dans des langages comme C.

Nous pouvons voir ++ou --comme plusieurs signes se multiplier, comme nous le faisons en mathématiques (-1) * (-1) = (+1).

Par exemple

---count

Analyse comme

-(-(-count)))

Ce qui se traduit par

-(+count)

Parce que la multiplication du -signe par le -signe est+

Et enfin,

-count
Anuj
la source
1
Qu'est-ce que cela signifie que les autres réponses ne font pas?
Daniel B.
@DanielB. D'autres réponses n'ont pas dit ce qui se passe en interne. Et, ils n'ont pas non plus dit ce qui se passera lorsque vous écrirez -----count.
Anuj
La première réponse acceptée le fait. ...
Daniel B.
2
Il n'y a aucune mention que la multiplication est en cours, j'ai donc pensé qu'un consice et au point la réponse serait utile pour les autres utilisateurs. Aucune infraction si vous avez compris cela. L'apprentissage est plus important que la source d'où vous apprenez.
Anuj
0

Dans python 3.8+, vous pouvez faire:

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

Vous pouvez faire beaucoup de réflexion avec cela.

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


1
2
3
4

Ou si vous voulez écrire quelque chose avec une syntaxe plus sophistiquée (le but n'est pas l'optimisation):

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


1
2
3
4

Il retourne bien 0 si un dos n'existe pas sans erreurs, puis le mettra à 1

Henri
la source