web-dev-qa-db-fra.com

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

Je remarque qu'un opérateur de pré-incrémentation/décrémentation peut être appliqué à une variable (comme ++count). Cela compile, mais cela 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 dévie-t-il du comportement de ces opérateurs vu en C/C++?

667
Ashwin Nanjappa

++ n'est pas un opérateur. Ce sont deux opérateurs +. L'opérateur + est l'opérateur identity, qui ne fait rien. (Précision: les opérateurs unaires + et - ne fonctionnent que sur des nombres, mais je suppose que vous ne vous attendriez pas à ce qu'un opérateur ++ hypothétique travaille sur des chaînes.)

++count

Analyses 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é laissés de côté pour des raisons de cohérence et de simplicité. Je ne connais pas l'argument exact que Guido van Rossum a donné pour la décision, mais je peux imaginer quelques arguments:

  • Analyse simplifiée. Techniquement, l’analyse ++count est ambiguë, dans la mesure où il pourrait s'agir de +, +, count (deux opérateurs unaires +) aussi facilement qu'il pourrait l'être ++, count (un opérateur unaire ++). Ce n'est pas une ambiguïté syntaxique significative, mais cela 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 += 1 dans les instructions inc de la plupart des ordinateurs. En cette journée d'optimisation des compilateurs et des langages interprétés en bytecode, l'ajout d'opérateurs à un langage permettant aux programmeurs d'optimiser leur code est généralement mal vu, en particulier dans un langage tel que Python, conçu pour être cohérent et lisible.
  • Effets secondaires déroutants. Une erreur courante pour les débutants dans les langues avec les opérateurs ++ est de mélanger les différences (de priorité et de valeur de retour) entre les opérateurs avant et après l'incrémentation/décrémentation, et Python aime éliminer les "gotcha" de langues. Les problèmes de précédence de pré/post-incrémentation en C _ sont plutôt poilus et incroyablement faciles à gâcher.
875
Chris Lutz

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

b++

Mais en Python, les entiers sont immuable . 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 augmentez a, vous augmenterez également b. Ce n'est pas ce que tu veux. Donc, vous devez réaffecter. Comme ça:

b = b + 1

Ou plus simple:

b += 1

Ce qui réaffectera 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 du C, 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 qu'en C , où vous les utilisez à chaque fois que vous avez une boucle, par exemple.

351
Lennart Regebro

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

Pour être exact, +x est évalué à x.__pos__() et ++x à x.__pos__().__pos__().

Je pourrais imaginer une structure de classe TRÈS étrange (Enfants, ne fais 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)
47
glglgl

Python ne possède 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

Dans une fonction, vous devez ajouter locals () en tant que second argument si vous souhaitez modifier la variable locale, sinon le système tentera de changer globalement.

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

Aussi avec ces fonctions, vous pouvez faire:

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 pour traduire javascript en python.

9
Piotr Dabkowski

En Python, une distinction entre les expressions et les déclarations est rigide forcé, contrairement aux langages tels que Common LISP, Scheme ou Rubis.

Wikipédia

Donc, en introduisant de tels opérateurs, vous diviseriez la division expression/déclaration. 

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

if x = 0:
  y = 1

comme vous pouvez le faire dans d'autres langues où une telle distinction n'est pas préservée.

8
Vitalii Fedorenko

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

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

Voici le:

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

MODIFIER:

Et puis il y a ce peu de comportement inattendu (et totalement 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é, une référence à l'objet est transmise à la fonction de formatage. Soupir. Alors:

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é dans sa forme actuelle par la verbosité (utilisez plutôt c.v) ...

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

TL; DR

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

a += 1

Plus de détails et de pièges

Mais soyez prudent ici. Si vous venez de C, même cela est différent en python. Python n'a pas de "variables" au sens de C, python utilise _/names et objects, et python ints est immuable.

alors disons que vous faites

a = 1

Cela signifie en python: créez un objet de type int ayant la valeur 1 et associez-lui le nom a. Object est une instance de int ayant la valeur 1, et namea y fait référence. Le nom a et l'objet auquel il fait référence sont distincts.

Maintenant, disons que vous faites 

a += 1

Puisque ints est immuable, voici ce qui se passe:

  1. rechercher l'objet auquel a fait référence (il s'agit d'une int avec id 0x559239eeb380)
  2. rechercher la valeur de l'objet 0x559239eeb380 (c'est 1)
  3. ajouter 1 à cette valeur (1 + 1 = 2)
  4. créer un objet new int avec la valeur 2 (il a l'identifiant d'objet 0x559239eeb3a0)
  5. relier le nom a à ce nouvel objet
  6. Désormais, a fait référence à l'objet 0x559239eeb3a0 et l'objet d'origine (0x559239eeb380) n'est plus désigné par le nom a. S'il n'y a pas d'autres noms faisant référence à l'objet d'origine, les ordures seront récupérées ultérieurement.

Essayez vous-même:

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