В чем разница между я = я + 1 и я + = 1 в цикле 'for'?
Сегодня я узнал любопытную вещь и задавался вопросом, может ли кто-нибудь пролить некоторый свет на то, в чем разница здесь?
import numpy as np
A = np.arange(12).reshape(4,3)
for a in A:
a = a + 1
B = np.arange(12).reshape(4,3)
for b in B:
b += 1
После запуска каждого цикла for
A
не изменился, но B
добавил один элемент к каждому элементу. Я фактически использую версию B
для записи в инициализированный массив NumPy в цикле for
.
Ответы
Ответ 1
Разница в том, что одна сама изменяет структуру данных (операция на месте) b += 1
, а другая просто переназначает переменную a = a + 1
.
Только для полноты:
x += y
не всегда выполняет операцию на месте, есть (по крайней мере) три исключения:
-
Если x
не реализует метод __iadd__
, то оператор x += y
является просто сокращением для x = x + y
. Это было бы так, если x
было чем-то вроде int
.
-
Если __iadd__
возвращает NotImplemented
, Python возвращается к x = x + y
.
-
Метод __iadd__
теоретически может быть реализован, чтобы не работать на месте. Было бы действительно странно это делать.
Как это бывает, ваш b
numpy.ndarray
, который реализует __iadd__
и возвращает себя, поэтому ваш второй цикл изменяет исходный массив на месте.
Подробнее об этом можно прочитать в документации на Python для "Эмуляция числовых типов" .
Эти методы [ __i*__
] вызываются для реализации расширенных арифметических заданий (+=
, -=
, *=
, @=
, /=
, //=
, %=
, **=
, <<=
, >>=
, &=
, ^=
, |=
). Эти методы должны пытаться выполнить операцию на месте (модифицировать "я" ) и вернуть результат (который может быть, но не обязательно, сам). Если конкретный метод не определен, расширенное присваивание возвращается к обычным методам. Например, если x является экземпляром класса с методом __iadd__()
, x += y
эквивалентен x = x.__iadd__(y)
. В противном случае рассматриваются x.__add__(y)
и y.__radd__(x)
, как и при оценке x + y
. В некоторых ситуациях расширенное назначение может привести к непредвиденным ошибкам (см. Почему a_tuple[i] += ["item"]
вызывает исключение, когда добавление работает?), но это поведение на самом деле является частью модель данных.
Ответ 2
В первом примере вы переназначаете переменную a
, а во втором вы изменяете данные на месте, используя оператор +=
.
Смотрите раздел 7.2.1. Операции расширенного присваивания:
Расширенное выражение присваивания типа x += 1
может быть переписано как x = x + 1
для достижения аналогичного, но не совсем равного эффекта. В расширенной версии x оценивается только один раз. Также, когда это возможно, фактическая операция выполняется на месте, а это означает, что вместо создания нового объекта и назначения этого объекта цели вместо него будет изменен старый объект.
Операторы +=
__iadd__
. Эта функция делает изменение на месте, и только после его выполнения результат возвращается к объекту, на который вы "применяете" +=
on.
__add__
, с другой стороны, принимает параметры и возвращает их сумму (без их модификации).
Ответ 3
Как уже указывалось, b += 1
обновляет b
на месте, в то время как a = a + 1
вычисляет a + 1
и затем присваивает имя a
результату (теперь a
больше не ссылается на строку A
).
Чтобы правильно понять оператор +=
, нам также нужно понять концепцию изменчивых и неизменяемых объектов. Посмотрите, что происходит, когда мы .reshape
:
C = np.arange(12)
for c in C:
c += 1
print(C) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
Мы видим, что C
не обновляется, это означает, что c += 1
и c = c + 1
эквивалентны. Это потому, что теперь C
является одномерным массивом (C.ndim == 1
), и поэтому при итерации по C
каждый целочисленный элемент извлекается и присваивается c
.
Теперь в Python целые числа являются неизменяемыми, что означает, что обновления на месте не допускаются, что фактически преобразует c += 1
в c = c + 1
, где c
теперь относится к новому целому числу, никак не связанному с C
Когда вы перебираете измененные массивы, целые строки (np.ndarray
's) назначаются одновременно b
(и a
), которые являются изменяемыми объектами, что означает, что вы можете вставлять новые целые числа по желанию, что происходит, когда Вы делаете a += 1
.
Следует отметить, что хотя предполагается, что +
и +=
связаны, как описано выше (и очень часто), любой тип может реализовать их любым способом, определив методы __add__
и __iadd__
соответственно.
Ответ 4
В короткой форме (a += 1
) есть возможность изменить a
на месте вместо того, чтобы создавать новый объект, представляющий сумму, и возвращать его обратно к тому же имени (a = a + 1
). Итак, короткий form (a += 1
) очень эффективен, так как не обязательно делать копию a
в отличие от a = a + 1
.
Также, даже если они выводят один и тот же результат, обратите внимание, что они разные, потому что они являются отдельными операторами: +
и +=
Ответ 5
Во-первых: переменные a и b в циклах относятся к объектам numpy.ndarray
.
В первом цикле a = a + 1
оценивается следующим образом: вызывается функция __add__(self, other)
numpy.ndarray
. Это создает новый объект и, следовательно, A не изменяется. После этого переменная a
устанавливается для ссылки на результат.
Во втором цикле новый объект не создается. Оператор b += 1
вызывает функцию __iadd__(self, other)
numpy.ndarray
, которая модифицирует объект ndarray
, на котором ссылается b. Следовательно, B
модифицируется.
Ответ 6
Ключевой проблемой здесь является то, что этот цикл повторяется по строкам (1-е измерение) B
:
In [258]: B
Out[258]:
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
In [259]: for b in B:
...: print(b,'=>',end='')
...: b += 1
...: print(b)
...:
[0 1 2] =>[1 2 3]
[3 4 5] =>[4 5 6]
[6 7 8] =>[7 8 9]
[ 9 10 11] =>[10 11 12]
Таким образом, +=
действует на изменяемый объект, массив.
Это подразумевается в других ответах, но легко пропущено, если ваш фокус находится на переназначении a = a+1
.
Я мог бы также внести изменения на B
на месте с индексированием [:]
или даже с чем-то более эффектным, b[1:]=0
:
In [260]: for b in B:
...: print(b,'=>',end='')
...: b[:] = b * 2
[1 2 3] =>[2 4 6]
[4 5 6] =>[ 8 10 12]
[7 8 9] =>[14 16 18]
[10 11 12] =>[20 22 24]
Конечно, с 2d-массивом, подобным B
, нам обычно не нужно перебирать строки. Многие операции, которые работают на одном из B
, также работают над всем этим. B += 1
, B[1:] = 0
и т.д.