В чем разница между я = я + 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) назначаются одновременно ba), которые являются изменяемыми объектами, что означает, что вы можете вставлять новые целые числа по желанию, что происходит, когда Вы делаете 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 и т.д.