Comportamento de operadores de incremento e decremento em Python

798

Percebo que um operador de pré-incremento / decremento pode ser aplicado a uma variável (como ++count). Compila, mas na verdade não altera o valor da variável!

Qual é o comportamento dos operadores de pré-incremento / decremento (++ / -) no Python?

Por que o Python se desvia do comportamento desses operadores visto em C / C ++?

Ashwin Nanjappa
fonte
19
Python não é C ou C ++. Decisões de design diferentes foram usadas para criar a linguagem. Em particular, o Python deliberadamente não define operadores de atribuição que podem ser usados ​​em uma expressão arbitrária; em vez disso, existem declarações de atribuição e declarações de atribuição aumentada. Veja a referência abaixo.
Ned Deily
8
O que fez você pensar que python tinha ++e --operadores?
U0b34a0f6ae 28/09/09
29
Kaizer: Vindo de C / C ++, escrevo ++ e ele compila em Python. Então, pensei que a linguagem tivesse operadores.
Ashwin Nanjappa 28/09/09
3
@Fox Você está assumindo um nível de planejamento e organização não em evidências
Básico
4
@mehaase ++ e - não existem em c "como açúcar sintático para aritmética de ponteiros", eles existem porque muitos processadores têm mecanismos automáticos de acesso à memória para incremento e decremento (em geral, indexação de ponteiros, indexação de pilhas) como parte de suas instruções nativas conjunto. Por exemplo, no 6809 assembler: sta x++... a instrução atômica resultante armazena o aacumulador para onde xestá apontando e depois é incrementada xpelo tamanho do acumulador. Isso é feito porque é mais rápido que a aritmética do ponteiro, porque é muito comum e é fácil de entender. Ambos pré e pós.
Fyngyrz 28/10

Respostas:

1059

++não é um operador. São dois +operadores. O +operador é o operador de identidade , que não faz nada. (Esclarecimento: os operadores unários +e -trabalham unicamente em números, mas presumo que você não esperaria que um ++operador hipotético trabalhasse em seqüências de caracteres.)

++count

Analisa como

+(+count)

O que se traduz em

count

Você precisa usar o +=operador um pouco mais para fazer o que deseja:

count += 1

Eu suspeito que os operadores ++e --foram deixados de fora por consistência e simplicidade. Não conheço o argumento exato que Guido van Rossum deu para a decisão, mas posso imaginar alguns argumentos:

  • Análise mais simples. Tecnicamente, a análise ++counté ambígua, como poderia ser +, +, count(dois unárias +operadores) tão facilmente como poderia ser ++, count(um unário ++operador). Não é uma ambiguidade sintática significativa, mas existe.
  • Linguagem mais simples. ++nada mais é do que um sinônimo += 1. Foi uma taquigrafia inventada porque os compiladores C eram estúpidos e não sabiam otimizar a += 1as incinstruções que a maioria dos computadores possui. Neste dia de otimização de compiladores e linguagens interpretadas por bytecode, a adição de operadores a uma linguagem para permitir que os programadores otimizem seu código geralmente é desaprovada, especialmente em uma linguagem como Python, projetada para ser consistente e legível.
  • Efeitos colaterais confusos. Um erro comum de novato em linguagens com ++operadores é misturar as diferenças (tanto na precedência quanto no valor de retorno) entre os operadores de pré e pós-incremento / decremento, e o Python gosta de eliminar a linguagem "pegadinha" -s. As questões de precedência do pré / pós-incremento em C são bem cabeludas e incrivelmente fáceis de estragar.
Chris Lutz
fonte
13
"O operador + é o operador" identidade ", que não faz nada." Somente para tipos numéricos; para outro tipo, é um erro por padrão.
newacct 28/09/09
45
Além disso, esteja ciente de que, em Python, + = e friends não são operadores que podem ser usados ​​em expressões. Em vez disso, em Python, eles são definidos como parte de uma "declaração de atribuição aumentada". Isso é consistente com a decisão de design de linguagem no Python para não permitir a atribuição ("=") como um operador em expressões arbitrárias, diferente do que se pode fazer em C. Consulte docs.python.org/reference/…
Ned Deily,
15
O +operador unário tem um uso. Para objetos decimais. Decimais, ele arredonda para a precisão atual.
u0b34a0f6ae 28/09/09
21
Estou apostando na simplificação do analisador. Observe um item no PEP 3099 , "Coisas que não serão alteradas no Python 3000": "O analisador não será mais complexo que o LL (1). Simples é melhor que o complexo. Essa idéia se estende ao analisador. Restringindo a gramática do Python a um analisador LL (1) é uma bênção, não uma maldição. Ele nos algema que nos impede de exagerar e acabar com regras gramaticais descoladas, como algumas outras linguagens dinâmicas que não terão nome, como Perl ". Não vejo como desambiguar + +e ++sem quebrar o LL (1).
Mike DeSimone
7
Não é correto dizer que isso ++nada mais é do que um sinônimo += 1. Existem variantes de pré-incremento e pós-incremento de ++, portanto, claramente não é a mesma coisa. Eu concordo com o resto de seus pontos, no entanto.
PhilHibbs
384

Quando você deseja incrementar ou diminuir, normalmente você deseja fazer isso em um número inteiro. Igual a:

b++

Mas em Python, números inteiros são imutáveis . Ou seja, você não pode mudá-los. Isso ocorre porque os objetos inteiros podem ser usados ​​sob vários nomes. Tente o seguinte:

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

aeb acima são realmente o mesmo objeto. Se você incrementasse a, também incrementaria b. Não é isso que você quer. Então você tem que reatribuir. Como isso:

b = b + 1

Ou mais simples:

b += 1

O qual será reatribuído bpara b+1. Esse não é um operador de incremento, porque não incrementa b, ele o reatribui.

Resumindo: o Python se comporta de maneira diferente aqui, porque não é C e não é um invólucro de baixo nível em torno do código de máquina, mas uma linguagem dinâmica de alto nível, onde incrementos não fazem sentido e também não são tão necessários quanto em C , onde você os usa sempre que tiver um loop, por exemplo.

Lennart Regebro
fonte
75
Esse exemplo está errado (e você provavelmente está confundindo imutabilidade com identidade) - eles têm o mesmo ID devido a alguma otimização vm que usa os mesmos objetos para números até 255 (ou algo assim). Por exemplo (números maiores): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc
56
A alegação de imutabilidade é espúria. Conceitualmente, i++significaria atribuir i + 1à variável i . i = 5; i++significa atribuir 6a i, não modificar o intobjeto apontado por i. Ou seja, não significa aumentar o valor de5 !
Caracol mecânico
3
@ Caracol mecânico: Nesse caso, não seriam operadores de incremento. E então o operador + = é mais claro, mais explícito, mais flexível e faz a mesma coisa de qualquer maneira.
Lennart Regebro 20/09/11
7
@LennartRegebro: em C ++ e Java, i++opera apenas em lvalues. Se se pretendesse incrementar o objeto apontado por i, essa restrição seria desnecessária.
Caracol mecânico
4
Acho esta resposta bastante desconcertante. Por que você está assumindo que ++ significaria algo além de uma abreviação de + = 1? É exatamente isso que significa em C (assumindo que o valor de retorno não seja usado). Você parece ter extraído algum outro significado do ar.
Don escotilha
52

Enquanto as outras respostas estão corretas na medida em que mostram o que um mero +geralmente faz (a saber, deixe o número como está, se for um), elas estão incompletas na medida em que não explicam o que acontece.

Para ser exato, +xavalia para x.__pos__()e ++xpara x.__pos__().__pos__().

Eu poderia imaginar uma estrutura de classe MUITO estranha (Filhos, não faça isso em casa!) Assim:

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

O Python não possui esses operadores, mas se você realmente precisar deles, poderá escrever uma função com a mesma funcionalidade.

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 uma função, você precisa adicionar locals () como um segundo argumento se quiser alterar a variável local, caso contrário, ela tentará mudar 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()

Também com estas funções você pode fazer:

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

Mas, na minha opinião, a seguinte abordagem é muito mais clara:

x = 1
x+=1
print(x)

Operadores de redução:

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

Eu usei essas funções no meu módulo traduzindo javascript para python.

Piotr Dabkowski
fonte
Nota: embora excelentes, esses métodos auxiliares não funcionarão se os seus locais existirem no quadro da pilha de funções de classe. ie - chamá-los de dentro de um método de classe def não funcionará - o ditado 'locals ()' é um instantâneo e não atualiza o quadro da pilha.
Adam
11

No Python, uma distinção entre expressões e instruções é aplicada rigidamente, em contraste com linguagens como Common Lisp, Scheme ou Ruby.

Wikipedia

Portanto, ao introduzir esses operadores, você quebraria a divisão de expressão / instrução.

Pela mesma razão, você não pode escrever

if x = 0:
  y = 1

como você pode em outros idiomas onde essa distinção não é preservada.

Vitalii Fedorenko
fonte
Curiosamente, essa restrição será levantada na próxima versão Python 3.8 com a nova sintaxe para expressões de atribuição (PEP-572 python.org/dev/peps/pep-0572 ). Poderemos escrever, if (n := len(a)) > 10: y = n + 1por exemplo. Note-se que a distinção é clara por causa da introdução de um novo operador para o efeito ( :=)
Zertrin
8

TL; DR

O Python não possui operadores de incremento / decremento unários ( --/ ++). Em vez disso, para incrementar um valor, use

a += 1

Mais detalhes e dicas

Mas tenha cuidado aqui. Se você vem de C, mesmo isso é diferente em python. O Python não possui "variáveis" no sentido em que C possui; em vez disso, o python usa nomes e objetos , e no python ints são imutáveis.

então vamos dizer que você faz

a = 1

O que isso significa em python é: crie um objeto do tipo intcom valor 1e vincule o nome aa ele. O objeto é uma instância de intter valor 1e o nome a se refere a ele. O nome ae o objeto ao qual ele se refere são distintos.

Agora vamos dizer que você faz

a += 1

Como ints são imutáveis, o que acontece aqui é o seguinte:

  1. procure o objeto que ase refere (é um intcom id 0x559239eeb380)
  2. procure o valor do objeto 0x559239eeb3801)
  3. adicione 1 a esse valor (1 + 1 = 2)
  4. crie um novo int objeto com valor 2(ele possui o ID do objeto 0x559239eeb3a0)
  5. religar o nome aa este novo objeto
  6. Agora ase refere ao objeto 0x559239eeb3a0e o objeto original ( 0x559239eeb380) não é mais referido pelo nome a. Se não houver outros nomes referentes ao objeto original, ele será coletado posteriormente.

Experimente você mesmo:

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

Sim, eu senti falta da funcionalidade ++ e - também. Alguns milhões de linhas de código c enraizaram esse tipo de pensamento na minha cabeça antiga e, em vez de combatê-lo ... Aqui está uma classe que eu desenvolvi que implementa:

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

Aqui 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

Você pode usá-lo assim:

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

... já tendo c, você poderia fazer isso ...

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

....ou apenas...

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

... e para (re) atribuição em número inteiro ...

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

... enquanto isso manterá c como contador de tipo:

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

EDITAR:

E também há esse comportamento inesperado (e completamente indesejado) ,

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

... porque dentro dessa tupla, getitem () não é o usado, em vez disso, uma referência ao objeto é passada para a função de formatação. Suspiro. Assim:

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

... ou, mais verbalmente e explicitamente, o que realmente queríamos que acontecesse, embora contra-indicado na forma real pela verbosidade (use em c.vvez disso) ...

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

Não há operadores pós / pré-incremento / decremento em python, como em linguagens como C.

Podemos ver ++ou --como múltiplos sinais sendo multiplicados, como fazemos em matemática (-1) * (-1) = (+1).

Por exemplo

---count

Analisa como

-(-(-count)))

O que se traduz em

-(+count)

Porque, multiplicação de -sinal com -sinal é+

E finalmente,

-count
Anuj
fonte
1
O que isso diz que outras respostas não dizem?
Daniel B.
@DanielB. Outras respostas não disseram o que acontece internamente. E eles também não disseram o que acontecerá quando você escrever -----count.
Anuj
A primeira resposta aceita aceita. ...
Daniel B.
2
Não há nenhuma menção de que a multiplicação está sendo realizada, então pensei que um consice e a resposta direta seriam úteis para outros usuários. Sem ofensa, se você entendeu disso. Aprender é mais importante que a fonte de onde você aprende.
Anuj 17/01
0

No python 3.8+, você pode fazer:

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

Você pode pensar muito com isso.

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


1
2
3
4

Ou, se você quiser escrever algo com sintaxe mais sofisticada (o objetivo não é otimização):

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


1
2
3
4

É bem retornado 0 se não existir sem erros e, em seguida, será definido como 1

Henry
fonte