Поведение операторов инкремента и декремента в Python
Вопрос на StackOverflow
Как использовать операторы предварительного инкремента и декремента (++
, --
), так же как в C++?
Почему ++count
выполняется, но не изменяет значение переменной?
5 ответ(ов)
Python не имеет операторов пре- и постинкремента.
В Python целые числа являются неизменяемыми. Это означает, что вы не можете изменять их. Причина в том, что объекты целых чисел могут использоваться под несколькими именами. Попробуйте следующее:
>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True
a и b на самом деле ссылаются на один и тот же объект. Если бы вы увеличили a, вы также увеличили бы b. Это не то, что вам нужно. Поэтому вам нужно делать переопределение. Например:
b = b + 1
Многие программисты с опытом работы в C, использующие Python, хотели оператор инкремента, но такой оператор выглядел бы так, как будто он увеличивает объект, в то время как на самом деле он просто переопределяет его. Поэтому были добавлены операторы -=
и +=
, чтобы быть короче, чем b = b + 1
, при этом оставаясь более понятными и гибкими, чем b++
. Таким образом, большинство людей используют:
b += 1
Этот код переопределяет b
значением b+1
. Это не оператор инкремента, потому что он не увеличивает b
, а переопределяет его.
В кратце: Python ведет себя иначе в этом отношении, потому что это не C, а не низкоуровневый интерфейс к машинному коду, а высокоуровневый динамический язык, где инкременты не имеют смысла и не так необходимы, как в C, где вы используете их каждый раз, когда у вас есть цикл, например.
Ответ на ваш вопрос можно сформулировать следующим образом:
Хотя другие ответы верны в том, что показывают, как работает оператор +
(то есть, оставляет число без изменений, если оно является числом), они не объясняют, что именно происходит под капотом.
Если быть точнее, выражение +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)) # Вывод: 430 <class '__main__.A'>
print(+x, type(+x)) # Вывод: called A.__pos__ \n 427 <class '__main__.B'>
print(++x, type(++x)) # Вывод: called A.__pos__ \n called B.__pos__ \n 446 <class '__main__.A'>
print(+++x, type(+++x)) # Вывод: called A.__pos__ \n called B.__pos__ \n called A.__pos__ \n 443 <class '__main__.B'>
В этом примере, когда вы вызываете +x
, вызывается метод __pos__
класса A
, который возвращает объект класса B
, а при двойном плюсе ++x
происходит два вызова методов __pos__
: сначала у класса A
, а затем у класса B
. Каждый новый вызов меняет значение, как видно из результирующих данных.
Таким образом, результат выполнения кода показывает, как многоразовое использование оператора +
ведёт к последовательным вызовам методов __pos__
, которые могут менять тип и значение объекта с каждым вызовом.
В Python 3.8 и выше вы можете использовать оператор := (оператор присваивания выражения), что позволяет реализовывать интересные конструкции. Вот несколько примеров:
(a := a + 1) # То же самое, что и ++a (инкремент, затем возвращает новое значение)
(a := a + 1) - 1 # То же самое, что и a++ (возвращает увеличенное значение минус 1) (бесполезно)
Вы можете делать с этим множество вещей.
Пример с циклом:
>>> 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, даже если переменная 'a' не существует, без ошибок, и затем устанавливает 'a' в 1.
Краткий обзор
В Python нет унарных операторов инкремента и декремента (--
/++
). Вместо этого, чтобы увеличить значение, используйте
a += 1
Подробнее и возможные подводные камни
Но будьте внимательны. Если вы пришли из C, то даже это в Python работает иначе. В Python нет "переменных" в том смысле, в котором они существуют в C. Вместо этого Python использует имена и объекты, при этом int
в Python являются неизменяемыми.
Итак, предположим, что вы написали
a = 1
Что это означает в Python: создается объект типа int
со значением 1
, и имя a
связывается с ним. Объект является экземпляром int
, имеющим значение 1
, а имя a
ссылается на него. Имя a
и объект, на который оно ссылается, являются различными сущностями.
Теперь допустим, вы написали
a += 1
Поскольку int
неизменяем, происходит следующее:
- вы получаете объект, на который указывает
a
(этоint
с идентификатором0x559239eeb380
) - получаете значение объекта
0x559239eeb380
(это1
) - прибавляете
1
к этому значению (1 + 1 = 2) - создается новый объект
int
со значением2
(у него идентификатор объекта0x559239eeb3a0
) - имя
a
переназначается на этот новый объект - Теперь
a
ссылается на объект0x559239eeb3a0
, а оригинальный объект (0x559239eeb380
) больше не ссылается именемa
. Если нет других имен, ссылающихся на оригинальный объект, он будет собран сборщиком мусора позже.
Попробуйте сами:
a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
Python не имеет операторов инкремента и декремента, но если они вам действительно нужны, вы можете написать функции, обеспечивающие аналогичную функциональность.
Вот пример реализации:
def PreIncrement(name, local={}):
# Эквивалентно ++name
if name in local:
local[name] += 1
return local[name]
globals()[name] += 1
return globals()[name]
def PostIncrement(name, local={}):
# Эквивалентно 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 и x равны 2
a = 1
b = PostIncrement('a') # b равно 1, а a равен 2
Важно помнить, что внутри функции нужно передать locals()
в качестве второго аргумента, если вы хотите изменить локальную переменную, иначе будет изменена глобальная переменная.
x = 1
def test():
x = 10
y = PreIncrement('x') # y будет 2, локальная x останется 10, а глобальная x изменится на 2
z = PreIncrement('x', locals()) # z будет 11, локальная x станет 11, а глобальная x останется неизменной
test()
Также с помощью этих функций можно делать следующее:
x = 1
print(PreIncrement('x')) # print(x+=1) является недопустимым!
Однако, на мой взгляд, следующий подход гораздо яснее:
x = 1
x += 1
print(x)
Что касается операторов декремента, вот аналогичные функции:
def PreDecrement(name, local={}):
# Эквивалентно --name
if name in local:
local[name] -= 1
return local[name]
globals()[name] -= 1
return globals()[name]
def PostDecrement(name, local={}):
# Эквивалентно name--
if name in local:
local[name] -= 1
return local[name] + 1
globals()[name] -= 1
return globals()[name] + 1
Я использовал эти функции в своем модуле для преобразования JavaScript в Python.
Почему используется string.join(list), а не list.join(string)?
Создание словаря с помощью генератора словарей
Как получить полный путь к директории текущего файла?
UnicodeDecodeError: Кодек 'charmap' не может декодировать байт X в позиции Y: символ отображается как <неопределённый>
Найти все файлы с расширением .txt в директории на Python