Comportamiento de operadores de incremento y decremento en Python

798

Noté que se puede aplicar un operador de pre-incremento / decremento en una variable (como ++count). ¡Compila, pero en realidad no cambia el valor de la variable!

¿Cuál es el comportamiento de los operadores de pre-incremento / decremento (++ / -) en Python?

¿Por qué Python se desvía del comportamiento de estos operadores visto en C / C ++?

Ashwin Nanjappa
fuente
19
Python no es C o C ++. Se tomaron diferentes decisiones de diseño para hacer el lenguaje. En particular, Python deliberadamente no define operadores de asignación que puedan usarse en una expresión arbitraria; más bien, hay declaraciones de asignación y declaraciones de asignación aumentadas. Ver referencia a continuación.
Ned Deily
8
¿Qué te hizo pensar que Python tenía ++y --operadores?
u0b34a0f6ae
29
Kaizer: Viniendo de C / C ++, escribo ++ count y se compila en Python. Entonces, pensé que el lenguaje tiene los operadores.
Ashwin Nanjappa
3
@Fox Estás asumiendo un nivel de planificación y organización que no está en evidencia
Básico
44
@mehaase ++ y - no existen en c "como azúcar sintáctica para la aritmética de puntero", existen porque muchos procesadores tienen mecanismos automáticos de acceso a la memoria para aumentar y disminuir (en general, indexación de puntero, indexación de pila) como parte de su instrucción nativa conjunto. Por ejemplo, en el ensamblador 6809: sta x++... la instrucción atómica que resulta almacena el aacumulador donde xestá apuntando, luego aumenta xsegún el tamaño del acumulador. Esto se hace porque es más rápido que la aritmética de puntero, porque es muy común y porque es fácil de entender. Tanto antes como después.
fyngyrz

Respuestas:

1060

++No es un operador. Son dos +operadores. El +operador es el operador de identidad , que no hace nada. (Aclaración: los operadores unarios +y -únicos solo funcionan en números, pero supongo que no esperarías que un ++operador hipotético trabaje en cadenas).

++count

Parses como

+(+count)

Lo que se traduce en

count

Tienes que usar el +=operador un poco más largo para hacer lo que quieres hacer:

count += 1

Sospecho que los operadores ++y --se quedaron fuera por coherencia y simplicidad. No sé el argumento exacto que Guido van Rossum dio para la decisión, pero puedo imaginar algunos argumentos:

  • Análisis más simple. Técnicamente, el análisis sintáctico ++countes ambigua, ya que podría ser +, +, count(dos unarios +operadores) tan fácilmente como podría ser ++, count(uno unario ++operador). No es una ambigüedad sintáctica significativa, pero existe.
  • Lenguaje más simple. ++no es más que sinónimo de += 1. Fue una taquigrafía inventada porque los compiladores de C eran estúpidos y no sabían cómo optimizar a += 1la incinstrucción que tienen la mayoría de las computadoras. En este día de optimización de compiladores y lenguajes interpretados por código de bytes, generalmente se desaprueba agregar operadores a un lenguaje para permitir que los programadores optimicen su código, especialmente en un lenguaje como Python que está diseñado para ser coherente y legible.
  • Efectos secundarios confusos. Un error de novato común en los idiomas con ++operadores es mezclar las diferencias (tanto en precedencia como en valor de retorno) entre los operadores de incremento / decremento previo y posterior, y a Python le gusta eliminar el lenguaje "gotcha" -s. Los problemas de precedencia del incremento previo / posterior en C son bastante complicados e increíblemente fáciles de confundir.
Chris Lutz
fuente
13
"El operador + es el operador de" identidad ", que no hace nada". Solo para tipos numéricos; para otro tipo es un error por defecto.
newacct
45
Además, tenga en cuenta que, en Python, + = y amigos no son operadores que se puedan usar en expresiones. Por el contrario, en Python se definen como parte de una "declaración de asignación aumentada". Esto es consistente con la decisión de diseño del lenguaje en Python de no permitir la asignación ("=") como operador dentro de expresiones arbitrarias, a diferencia de lo que uno puede hacer en C. Ver docs.python.org/reference/…
Ned Deily
15
El +operador unario tiene un uso. Para objetos decimales Decimales, se redondea a la precisión actual.
u0b34a0f6ae
21
Estoy apostando por la simplificación del analizador. Observe un elemento en PEP 3099 , "Cosas que no cambiarán en Python 3000": "El analizador no será más complejo que LL (1). Simple es mejor que complejo. Esta idea se extiende al analizador. Restringiendo la gramática de Python a un analizador LL (1) es una bendición, no una maldición. Nos pone esposas que nos impiden ir por la borda y terminar con reglas gramaticales funky como algunos otros lenguajes dinámicos que quedarán sin nombre, como Perl ". No veo cómo desambiguar + +y ++sin romper LL (1).
Mike DeSimone
77
No es correcto decir que ++no es más que un sinónimo de += 1. Hay variantes de incremento previo y posterior de ++, por lo que claramente no es lo mismo. Sin embargo, estoy de acuerdo con el resto de tus puntos.
PhilHibbs
384

Cuando desea aumentar o disminuir, normalmente desea hacerlo en un número entero. Al igual que:

b++

Pero en Python, los enteros son inmutables . Es que no puedes cambiarlos. Esto se debe a que los objetos enteros se pueden usar con varios nombres. Prueba esto:

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

ayb anteriores son en realidad el mismo objeto. Si incrementaras a, también incrementarías b. Eso no es lo que quieres. Entonces tienes que reasignar. Me gusta esto:

b = b + 1

O más simple:

b += 1

Que reasignará ba b+1. Ese no es un operador de incremento, porque no incrementa b, lo reasigna.

En resumen: Python se comporta de manera diferente aquí, porque no es C, y no es un contenedor de bajo nivel alrededor del código de máquina, sino un lenguaje dinámico de alto nivel, donde los incrementos no tienen sentido, y tampoco son tan necesarios como en C , donde los usa cada vez que tiene un bucle, por ejemplo.

Lennart Regebro
fuente
75
Ese ejemplo es incorrecto (y probablemente esté confundiendo la inmutabilidad con la identidad): tienen la misma identificación debido a alguna optimización de vm que usa los mismos objetos para números hasta 255 (o algo así). Por ejemplo (números más grandes): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc
56
La afirmación de inmutabilidad es espuria. Conceptualmente, i++significaría asignar i + 1a la variable i . i = 5; i++significa asignar 6a i, no modificar el intobjeto señalado por i. Es decir, no significa incrementar el valor de5 !
Caracol mecánico
3
Caracol @Mechanical: en cuyo caso no serían operadores de incremento en absoluto. Y luego el operador + = es más claro, más explícito, más flexible y hace lo mismo de todos modos.
Lennart Regebro
77
@LennartRegebro: en C ++ y Java, i++solo funciona con valores. Si se pretendiera incrementar el objeto señalado por i, esta restricción sería innecesaria.
Caracol mecánico
44
Esta respuesta me parece bastante desconcertante. ¿Por qué supones que ++ significaría algo más que una taquigrafía para + = 1? Eso es precisamente lo que significa en C (suponiendo que no se use el valor de retorno). Parece que has sacado algún otro significado del aire.
Don Hatch
52

Si bien las otras respuestas son correctas en la medida en que muestran lo que +generalmente hace un simple (es decir, dejar el número como está, si es uno), están incompletas en la medida en que no explican lo que sucede.

Para ser exactos, +xevalúa a x.__pos__()y ++xa x.__pos__().__pos__().

Me imagino una estructura de clase MUY extraña (¡Niños, no hagan esto en casa!) Como esta:

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
fuente
13

Python no tiene estos operadores, pero si realmente los necesita, puede escribir una función que tenga la misma funcionalidad.

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

Dentro de una función, debe agregar locales () como segundo argumento si desea cambiar la variable local, de lo contrario, intentará cambiar 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()

También con estas funciones puedes hacer:

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

Pero en mi opinión, el siguiente enfoque es mucho más claro:

x = 1
x+=1
print(x)

Operadores de decremento:

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

Usé estas funciones en mi módulo traduciendo javascript a python.

Piotr Dabkowski
fuente
Nota: si bien es excelente, estos métodos auxiliares no funcionarán si sus locales existen en el marco de la pila de funciones de clase. es decir, llamarlos desde un método de clase def no funcionará: el dict 'locals ()' es una instantánea y no actualiza el marco de la pila.
Adam
11

En Python, se hace una distinción rígida entre expresiones y declaraciones, en contraste con lenguajes como Common Lisp, Scheme o Ruby.

Wikipedia

Entonces, al introducir dichos operadores, rompería la división expresión / declaración.

Por la misma razón que no puedes escribir

if x = 0:
  y = 1

como puede en otros idiomas donde no se conserva esa distinción.

Vitalii Fedorenko
fuente
Curiosamente, esta restricción se eliminará en la próxima versión Python 3.8 con la nueva sintaxis para expresiones de asignación (PEP-572 python.org/dev/peps/pep-0572 ). Podremos escribir if (n := len(a)) > 10: y = n + 1por ejemplo. Tenga en cuenta que la distinción es clara debido a la introducción de un nuevo operador para ese propósito ( :=)
Zertrin
8

TL; DR

Python no tiene operadores de incremento / decremento unarios ( --/ ++). En cambio, para incrementar un valor, use

a += 1

Más detalles y gotchas

Pero ten cuidado aquí. Si vienes de C, incluso esto es diferente en Python. Python no tiene "variables" en el sentido de que C sí, sino que usa nombres y objetos , y en python ints son inmutables.

así que digamos que lo haces

a = 1

Lo que esto significa en python es: crear un objeto de tipo que inttenga valor 1y vincularlo con el nombre a. El objeto es una instancia de inttener valor 1, y el nombre se a refiere a él. El nombre ay el objeto al que se refiere son distintos.

Ahora digamos que lo haces

a += 1

Como ints son inmutables, lo que sucede aquí es lo siguiente:

  1. busque el objeto al que se arefiere (es un intcon id 0x559239eeb380)
  2. buscar el valor del objeto 0x559239eeb380(es 1)
  3. agregue 1 a ese valor (1 + 1 = 2)
  4. crear un nuevo int objeto con valor 2(tiene id de objeto 0x559239eeb3a0)
  5. volver a unir el nombre aa este nuevo objeto
  6. Ahora se arefiere al objeto 0x559239eeb3a0y 0x559239eeb380el nombre ya no hace referencia al objeto original ( ) a. Si no hay otros nombres que se refieran al objeto original, se recolectará basura más tarde.

Pruébalo tú mismo:

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

Sí, me perdí la funcionalidad ++ y - también. Unos pocos millones de líneas de código c graban ese tipo de pensamiento en mi vieja cabeza, y en lugar de luchar contra él ... Aquí hay una clase que improvisé que implementa:

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

Aquí está:

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

Puede usarlo así:

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

... ya teniendo c, podrías hacer esto ...

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

....o solo...

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

... y para (re) asignación en entero ...

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

... mientras que esto mantendrá c como contador de tipo:

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

EDITAR:

Y luego está este comportamiento inesperado (y completamente no deseado) ,

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

... porque dentro de esa tupla, getitem () no es lo que se usó, sino que se pasa una referencia al objeto a la función de formateo. Suspiro. Entonces:

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

... o, más detalladamente, y explícitamente lo que realmente queríamos que sucediera, aunque la verbosidad lo contrarresta en forma real (use c.ven su lugar) ...

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

No hay operadores post / pre incremento / decremento en python como en lenguajes como C.

Podemos ver ++o --multiplicar signos múltiples, como lo hacemos en matemáticas (-1) * (-1) = (+1).

P.ej

---count

Parses como

-(-(-count)))

Lo que se traduce en

-(+count)

Porque, la multiplicación de -signo con -signo es+

Y finalmente,

-count
Anuj
fuente
1
¿Qué dice esto que otras respuestas no?
Daniel B.
@DanielB. Otras respuestas no han dicho lo que sucede internamente. Y tampoco han dicho lo que sucederá cuando escribas -----count.
Anuj
La primera respuesta aceptada sí. ...
Daniel B.
2
No hay ninguna mención de que se esté llevando a cabo la multiplicación, por lo que pensé que una respuesta coherente y directa sería útil para otros usuarios. Sin ofender si entendiste de eso. Aprender es más importante que la fuente de donde aprendes.
Anuj
0

En python 3.8+ puedes hacer:

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

Puedes pensar mucho con esto.

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


1
2
3
4

O si desea escribir algo con una sintaxis más sofisticada (el objetivo no es la optimización):

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


1
2
3
4

Bien devuelve 0 si no existe un error sin errores, y luego lo establecerá en 1

Enrique
fuente