Поведение инкрементных и декрементных операторов в Python

798

Я заметил, что к переменной (например ++count) можно применить оператор предварительного увеличения / уменьшения . Он компилируется, но фактически не меняет значение переменной!

Каково поведение операторов предварительного увеличения / уменьшения (++ / -) в Python?

Почему Python отличается от поведения этих операторов в C / C ++?

Эшвин Нанджаппа
источник
19
Python не является C или C ++. Различные дизайнерские решения вошли в создание языка. В частности, Python намеренно не определяет операторы присваивания, которые можно использовать в произвольном выражении; скорее есть операторы присваивания и дополненные операторы присваивания. Смотрите ссылку ниже.
Нед Дейли
8
Что заставило вас думать питона были ++и --операторы?
u0b34a0f6ae
29
Kaizer: Исходя из C / C ++, я пишу ++ count и он компилируется в Python. Итак, я думал, что в языке есть операторы.
Эшвин Нанджаппа
3
@Fox Вы предполагаете, что уровень планирования и организации не очевиден
Базовый
4
@mehaase ++ и - не существуют в c «как синтаксический сахар для арифметики указателей», они существуют потому, что многие процессоры имеют автоматические механизмы увеличения и уменьшения доступа к памяти (в общем, индексирование указателей, индексирование стека) как часть их собственных инструкций набор. Например, в ассемблере 6809: sta x++... атомарная инструкция, которая в результате сохраняет aаккумулятор, на xкоторый указывает, а затем увеличивается xна размер аккумулятора. Это сделано потому, что это быстрее, чем арифметика указателей, потому что это очень распространено, и потому что это легко понять. Оба до и после.
fyngyrz

Ответы:

1060

++не оператор. Это два +оператора. +Оператор является личность оператора, который ничего не делает. (Пояснение: унарные операторы +and и -работают только с числами, но я предполагаю, что вы не ожидаете, что гипотетический ++оператор будет работать со строками.)

++count

Разбирает как

+(+count)

Что переводится как

count

Вы должны использовать немного более длинный +=оператор, чтобы сделать то, что вы хотите сделать:

count += 1

Я подозреваю, что операторы ++and --были исключены для последовательности и простоты. Я не знаю точного аргумента, который Гвидо ван Россум дал для решения, но я могу представить несколько аргументов:

  • Проще разбирать. С технической точки зрения синтаксического анализа ++countнеоднозначно, так как это может быть +, +, count(две унарные +операторы) так же легко , как это может быть ++, count(один унарный ++оператор). Это не значительная синтаксическая неоднозначность, но она существует.
  • Упрощенный язык. ++не более чем синоним += 1. Это сокращенная изобретены потому , что компиляторы были глупы и не знают , как оптимизировать a += 1в incинструкцию большинство компьютеров имеют. В этот день оптимизации компиляторов и интерпретируемых языков байт-кода добавление операторов к языку, позволяющее программистам оптимизировать их код, обычно не одобряется, особенно в языке, подобном Python, который разработан, чтобы быть последовательным и читаемым.
  • Смущающие побочные эффекты. Одной из распространенных ошибок новичков в языках с ++операторами является смешение различий (как по приоритету, так и по возвращаемому значению) между операторами до и после увеличения / уменьшения, и Python любит исключать языковые "уловки". В вопросы старшинства по предварительной / пост-инкремент в C довольно волосатый, и невероятно легко испортить.
Крис Лутц
источник
13
«Оператор + - это оператор« идентичности », который ничего не делает». Только для числовых типов; для другого типа это ошибка по умолчанию.
newacct
45
Также имейте в виду, что в Python + = и друзья не являются операторами, которые можно использовать в выражениях. Скорее, в Python они определены как часть «расширенного оператора присваивания». Это согласуется с решением о дизайне языка в Python запретить присваивание ("=") в качестве оператора в произвольных выражениях, в отличие от того, что можно сделать в C. См. Docs.python.org/reference/…
Ned Deily
15
Унарный +оператор имеет применение. Для десятичных. Десятичных объектов округляется до текущей точности.
u0b34a0f6ae
21
Я ставлю на упрощение парсера. Обратите внимание на элемент в PEP 3099 , «Вещи, которые не изменятся в Python 3000»: «Парсер не будет более сложным, чем LL (1). Простой лучше, чем сложный. Эта идея распространяется на синтаксический анализатор. Ограничение грамматики Python синтаксический анализатор LL (1) - это благословение, а не проклятие. Он ставит нас в наручники, которые не позволяют нам переборщить и заканчивают в стиле фанк грамматических правил, как некоторые другие динамические языки, которые останутся безымянными, такие как Perl. " Я не вижу, как устранить неоднозначность + +и ++без нарушения LL (1).
Майк ДеСимоне,
7
Неправильно говорить, что ++это не что иное, как синоним += 1. Существуют до + и постинкрементные варианты ++, так что это явно не одно и то же. Я согласен с остальными вашими пунктами, хотя.
PhilHibbs
384

Когда вы хотите увеличить или уменьшить, вы обычно хотите сделать это на целое число. Вот так:

b++

Но в Python целые числа неизменны . То есть ты не можешь их изменить. Это потому, что целочисленные объекты могут быть использованы под несколькими именами. Попробуй это:

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

А и В выше на самом деле один и тот же объект. Если вы увеличиваете a, вы также увеличиваете b. Это не то, что вы хотите. Таким образом, вы должны переназначить. Нравится:

b = b + 1

Или проще:

b += 1

Который будет переназначен bна b+1. Это не оператор приращения, потому что он не увеличивается b, а переназначает его.

Вкратце: Python ведет себя по-другому, потому что это не C, и это не оболочка низкого уровня для машинного кода, а динамический язык высокого уровня, где приращения не имеют смысла, а также не так необходимы, как в C где вы используете их каждый раз, когда у вас есть цикл, например.

Леннарт Регебро
источник
75
Этот пример неверен (и вы, вероятно, путаете неизменность с идентичностью) - у них одинаковый идентификатор из-за некоторой оптимизации vm, которая использует те же объекты для чисел до 255 (или что-то в этом роде). Например (большие цифры): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc
56
Требование неизменности является поддельным. Концептуально, i++означало бы назначить i + 1на переменную i . i = 5; i++средство для назначения 6на i, не изменяем intобъект , на который указывает i. То есть это не значит увеличивать значение5 !
Механическая улитка
3
@ Механическая улитка: в этом случае это не будут операторы приращения вообще. И тогда оператор + = становится более понятным, более явным, более гибким и в любом случае делает то же самое.
Леннарт Регебро
7
@LennartRegebro: в C ++ и Java i++работает только с lvalues. Если бы это было предназначено для увеличения объекта, на который указывает i, это ограничение было бы ненужным.
Механическая улитка
4
Я нахожу этот ответ довольно странным. Почему вы предполагаете, что ++ будет означать что-то кроме сокращения для + = 1? Это именно то, что это означает в C (при условии, что возвращаемое значение не используется). Вы, кажется, вытащили из воздуха какое-то другое значение.
Дон Хэтч
52

В то время как ответы других верны, поскольку они показывают, что +обычно делает простое (а именно, оставляют число как есть, если оно единичное), они неполны, поскольку не объясняют, что происходит.

Чтобы быть точным, имеет +xзначение x.__pos__()и ++xдля x.__pos__().__pos__().

Я мог бы представить ОЧЕНЬ странную структуру класса (Дети, не делайте этого дома!), Вот так:

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
источник
13

В Python нет этих операторов, но если они вам действительно нужны, вы можете написать функцию с той же функциональностью.

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

Применение:

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

Внутри функции вы должны добавить locals () в качестве второго аргумента, если вы хотите изменить локальную переменную, иначе она попытается изменить глобальную переменную.

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

Также с этими функциями вы можете сделать:

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

Но, на мой взгляд, следующий подход гораздо понятнее:

x = 1
x+=1
print(x)

Операторы декремента:

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

Я использовал эти функции в своем модуле, переводя javascript на python.

Петр Дабковский
источник
Примечание: хотя эти вспомогательные методы великолепны, они не будут работать, если ваши локальные объекты существуют в кадре стека функций класса. то есть - вызов их из метода класса def не будет работать - диктант 'locals ()' является снимком и не обновляет кадр стека.
Адам
11

В Python различие между выражениями и утверждениями строго соблюдается, в отличие от таких языков, как Common Lisp, Scheme или Ruby.

Википедия

Таким образом, введя такие операторы, вы нарушите разбиение выражения / оператора.

По той же причине, по которой ты не можешь писать

if x = 0:
  y = 1

как вы можете в некоторых других языках, где такое различие не сохраняется.

Виталий Федоренко
источник
Интересно, что это ограничение будет снято в следующем выпуске Python 3.8 с новым синтаксисом для выражений присваивания (PEP-572 python.org/dev/peps/pep-0572 ). Мы сможем написать, if (n := len(a)) > 10: y = n + 1например. Обратите внимание, что различие очевидно из-за введения нового оператора для этой цели ( :=)
Zertrin
8

TL; DR

Python не имеет унарных операторов увеличения / уменьшения ( --/ ++). Вместо этого, чтобы увеличить значение, используйте

a += 1

Больше деталей и ошибок

Но будьте осторожны здесь. Если вы пришли из C, даже это не так в Python. Python не имеет «переменных» в том смысле, в каком это есть в C, вместо этого python использует имена и объекты , а в Python ints являются неизменяемыми.

так скажем, вы делаете

a = 1

В Python это означает следующее: создайте объект со intзначением типа 1и привяжите aк нему имя . Объект является экземпляром , intимеющим значение 1, а название a относится к нему. Имя aи объект, к которому оно относится, различны.

Теперь скажем, что вы делаете

a += 1

Поскольку ints неизменяемы, то, что здесь происходит, выглядит следующим образом:

  1. найдите объект, на который aссылается (это intс идентификатором 0x559239eeb380)
  2. посмотрите значение объекта 0x559239eeb380(оно есть 1)
  3. добавить 1 к этому значению (1 + 1 = 2)
  4. создать новый int объект со значением 2(у него есть идентификатор объекта 0x559239eeb3a0)
  5. привязать имя aк этому новому объекту
  6. Теперь aотносится к объекту, 0x559239eeb3a0а исходный объект ( 0x559239eeb380) больше не упоминается по имени a. Если нет других имен, относящихся к исходному объекту, это будет сборщик мусора позже.

Попробуйте сами:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
RBF06
источник
6

Да, я пропустил ++ и - функциональность. Несколько миллионов строк кода на C укоренили такое мышление в моей старой голове, и вместо того, чтобы бороться с ним ... Вот класс, который я создал, который реализует:

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

Вот это:

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

Вы можете использовать это так:

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

... уже имея с, вы могли бы сделать это ...

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

....или просто...

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

... и для (пере) назначения в целое число ...

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

... пока это будет поддерживать c как счетчик типа:

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

РЕДАКТИРОВАТЬ:

И затем есть это немного неожиданного (и совершенно нежелательного) поведения ,

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

... потому что внутри этого кортежа getitem () не то, что использовалось, а ссылка на объект передается в функцию форматирования. Вздох. Так:

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

... или, более подробно и явно, то, что мы на самом деле хотели случиться, хотя в реальной форме противопоставлено многословию (используйте c.vвместо) ...

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
fyngyrz
источник
2

В python нет операторов post / pre приращения / декремента, как в таких языках, как C.

Мы можем видеть ++или --как умножаются несколько знаков, как мы делаем в математике (-1) * (-1) = (+1).

Например

---count

Разбирает как

-(-(-count)))

Что переводится как

-(+count)

Потому что умножение -знака со -знаком+

И наконец,

-count
Ануй
источник
1
Что это говорит о том, что другие ответы нет?
Даниэль Б.
@DanielB. Другие ответы не сказали, что происходит внутри. И ни один из них не рассказал, что будет, когда ты напишешь -----count.
Ануй
Первый, принятый ответ делает. ...
Даниэль Б.
2
Нет никаких упоминаний о том, что умножение выполняется, поэтому я подумал, что совет и точный ответ будут полезны для других пользователей. Без обид, если вы поняли из этого. Обучение важнее источника, из которого вы учитесь.
Ануй
0

В Python 3.8+ вы можете сделать:

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

С этим можно много думать.

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


1
2
3
4

Или, если вы хотите написать что-то с более сложным синтаксисом (цель не в оптимизации):

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


1
2
3
4

Он хорошо возвращает 0, если DOS не существует без ошибок, а затем установит его на 1

Генри
источник