

後半のインプレース演算の意味はわかるのですが、`x` が一度しか評価されないという意味がわかりません。  
値については、どちらの方法でもxの初期値とx+1の評価をすることにはかわりないと思うのですが。

[7.2.1. 累算代入文 (augmented assignment
statement)](https://docs.python.org/ja/3/reference/simple_stmts.html#grammar-
token-augmented-assignment-stmt)

> `x += 1` のような累算代入式は、 `x = x + 1`
> のように書き換えてほぼ同様の動作にできますが、厳密に等価にはなりません。累算代入の方では、 `x`
> は一度しか評価されません。また、実際の処理として、可能ならば インプレース (in-place)
> 演算が実行されます。これは、代入時に新たなオブジェクトを生成してターゲットに代入するのではなく、以前のオブジェクトの内容を変更するということです。

> An augmented assignment expression like x += 1 can be rewritten as x = x + 1
> to achieve a similar, but not exactly equal effect. In the augmented
> version, x is only evaluated once. Also, when possible, the actual operation
> is performed in-place, meaning that rather than creating a new object and
> assigning that to the target, the old object is modified instead.

皆さんの意見を加味した追加情報です。

## int計算

どちらでも計算時間かわらなかったので省略

次はリストに要素追加していくバージョン。

## リスト（累算）

augmentassign.py

計算にかかった時間：0.0144632940292358398[sec]

[code]

    import time
    firstTime = time.time()
    i=0
    list1=[1,1,1]
    while i <100_000:
        i+=1
        list1 +=[1]
    
    usedTime = time.time() - firstTime
    print ("計算にかかった時間:{0}".format(usedTime) + "[sec]")
    
[/code]

## リスト（ノーマル）

normalassign.py

計算にかかった時間:25.47399997711816[sec]

[code]

    import time
    firstTime = time.time()
    i=0
    list1=[1,1,1]
    while i <100_000:
        i+=1
        list1=list1+[1]
    
    usedTime = time.time() - firstTime
    print ("計算にかかった時間:{0}".format(usedTime) + "[sec]")
    
[/code]

これは単純にin-placeかどうかで圧倒的な差がついているのかと思われます。

## タプル

単なる要素の追加ではどちらでも計算時間かわらなかったので省略  
どちらもout-placeだからでしょうか。

それでは、もっと元になるオペランド自体の計算量を増やしてみることにします。  
念のためにlistのidがかわっていないことの確認を入れています。

## オペランドに計算要素をもたせたリスト（ノーマル型）

evalOnLeft_normal.py

計算前のid:140108440645824  
list1:[1000001, 1000001, 1000001, 1000001, 1000001, 1000001, 1000001, 1000001,
1000001, 1000001]  
計算後のid:140108440645824  
計算にかかった時間:3.2987570762634277[sec]

[code]

    import time
    
    firstTime = time.time()
    i=0
    list1=[1,1,1,1,1,1,1,1,1,1]
    print("計算前のid:{0}".format(id(list1)))
    
    def evalOnLeft():
        global i
        whichIndex=i%10
        return whichIndex
    
    
    while i <10_000_000:
      
        list1[evalOnLeft()]=list1[evalOnLeft()]+1
        i+=1
    
    usedTime = time.time() - firstTime
    print("list1:{0}".format(list1))
    print("計算後のid:{0}".format(id(list1)))
    print("計算にかかった時間:{0}".format(usedTime) + "[sec]")
    
[/code]

## オペランドに計算要素をもたせたリスト（累算型）

evalOnLeft_augment.py

計算前のid:140579620745408  
list1:[1000001, 1000001, 1000001, 1000001, 1000001, 1000001, 1000001, 1000001,
1000001, 1000001]  
計算後のid:140579620745408  
計算にかかった時間:2.5201609134674072[sec]

[code]

    import time
    
    firstTime = time.time()
    i=0
    list1=[1,1,1,1,1,1,1,1,1,1]
    print("計算前のid:{0}".format(id(list1)))
    
    def evalOnLeft():
        global i
        whichIndex=i%10
        return whichIndex
    
    
    while i <10_000_000:
      
        list1[evalOnLeft()]+=1
        i+=1
    
    usedTime = time.time() - firstTime
    print("list1:{0}".format(list1))
    print("計算後のid:{0}".format(id(list1)))
    print("計算にかかった時間:{0}".format(usedTime) + "[sec]")
    
[/code]

ということで、もっともらしい時間差がつきました。  
オペランド自体の計算量が増えれば、気にする価値が出てくるかもしれません。

あとは、これを累算型とノーマル型で「評価回数」が異なるというかどうかの言葉の定義であったり、実際docの作成者の意図がどうであったかの問題かもしれません。


CC BY-SA 4.0
2021-04-03T07:36:11.180
75061
2021-04-07T05:42:59.223
2021-04-04T02:00:15.223
40961
40961
question
python python3
x += 1といった累算代入文でxが一度しか評価されないというのはどういう意味ですか？


代入文との比較の話だと思います。  
「`x = x + 1` の場合は右辺の `x` と左辺の `x` の計2回評価するけど、`x += 1` では1回だけだから厳密 (内部の処理的)
に等価にはなりません。」  
程度の意味ではないでしょうか。


CC BY-SA 4.0
2021-04-03T09:49:06.000
75064
2021-04-03T10:18:15.867
2021-04-03T10:18:15.867
13227
13227
75061
answer


> x += 1といった累算代入文でxが一度しか評価されないというのはどういう意味ですか？

私は「代入」も評価に数えていると解釈しました。

  * x = x + 1の場合

x + 1を計算する --> xを評価(1回目の評価)  
結果をxに代入する --> xを評価(2回目の評価)

  * x += 1の場合

xに1を足し込む --> xを評価

しかしながら

> x += 1 のような累算代入式は、 x = x + 1 のように書き換えてほぼ同様の動作にできますが、厳密に等価にはなりません。累算代入の方では、x
> は一度しか評価されません。

の「累算代入の方では、x は一度しか評価されません。」は余計な説明だと思います(※)。  
「厳密に等価にならない」のは「評価回数の違い」で起こるのではなく、生成した新たなオブジェクトでxを書き換えているか(代入)、いないか(累算代入)の違いだと思います。

※そう思ったのは「評価の回数」で振る舞いが変わる例に思い至らなかったためです。

* * *

### 追記

「累算代入の方では、x は一度しか評価されません。」はやっぱり怪しいです。

Python Documentationを調べてみましたが

> x += 1はx = operator.iadd(x, 1)に等価  
>  x = x + 1はx = operator.add(x, 1)に等価

とありました。  
これによればインプレース版でもそうでなくてもxの評価回数は同じように読めます。

また、クラスのプロパティにprint文を入れて実行したところ、どちらも評価回数は同じでした。

以下は出典と確認結果です。

* * *

[operator ---
関数形式の標準演算子](https://docs.python.org/ja/3.9/library/operator.html?highlight=iadd)  
に次の説明がありました。

> operator モジュールは、Python の組み込み演算子に対応する効率的な関数群を提供します。 例えば、 operator.add(x, y)
> は式 x+y と等価です

これによれば`x = x + 1`は`x = operator.add(x, y)`に等価です。

[インプレース (in-place)
演算子](https://docs.python.org/ja/3.9/library/operator.html#in-place-operators)  
には次の説明がありました。

> 多くの演算に「インプレース」版があります。 以下の関数はそうした演算子の通常の文法に比べてより素朴な呼び出し方を提供します。たとえば、 文 x += y
> は x = operator.iadd(x, y) と等価です。別の言い方をすると、 z = operator.iadd(x, y) は複合文 z =
> x; z += y と等価です。

これによれば`x += 1`は`x = operator.iadd(x, 1)`に等価です。

【確認に使用したコード】

[code]

    class Sample():
        def __init__(self, x):
            self.xx = x
        @property
        def x(self):
            print('get x ==>', self.xx)
            return self.xx
        @x.setter
        def x(self, x):
            print('set x <==', x)
            self.xx = x
    
    sample = Sample(12)
    sample.x += 1
    print(sample.x)
    sample.x = sample.x + 1
    print(sample.x)
    
[/code]

【実行結果】

[code]

    get x ==> 12
    set x <== 13
    get x ==> 13
    13
    get x ==> 13
    set x <== 14
    get x ==> 14
    14
    
[/code]

* * *

### 追記２

> 「累算代入の方では、x は一度しか評価されません。」はやっぱり怪しいです。

は撤回いたします。

dosecさん、OOPerさん、peppaaさん、oriri 5さんの回答やコメントから

> x += 1 のような累算代入式は、 x = x + 1 のように書き換えてほぼ同様の動作にできますが、厳密に等価にはなりません。累算代入の方では、 x
> は一度しか評価されません。

は「+=」や「+」の演算の前に「x」を評価する回数が異なると解釈すれば、「x は一度しか評価されません」は納得がいきます。

f()が呼ばれるごとに値が異なる場合  
「x[f()] = x[f()] + 1」と「x[f()] += 1」の結果が異なるのは「x[f()]の評価回数が異なるためといっていいと思います。


CC BY-SA 4.0
2021-04-03T10:48:47.597
75065
2021-04-03T23:38:31.433
2021-04-03T23:38:31.433
35558
35558
75061
answer


「`x`
が一度しか評価されない」については、字句通りに`x`を1つの識別子として理解するのではなく、累積代入文の左辺に来得る任意のものを表していると解釈した方がわかりやすくなるのではないでしょうか。

[code]

    i = 0
    def inc():
        global i
        i += 1
        return i
    list = [1, 2, 3]
    list[inc()] += 1
    print(list) //->[1, 3, 3]
    
[/code]

このような例であれば、`list[inc()] += 1`と`list[inc()] = list[inc()] +
1`の違いははっきりとわかるかと思います。


CC BY-SA 4.0
2021-04-03T12:28:04.783
75066
2021-04-03T12:28:04.783
13972
75061
answer


左辺 `x` に副作用があるとき「一度だけ評価」が規定されていないと困ったことになります

他言語でよければ [c](/questions/tagged/c "'c' のタグが付いた質問を表示") の例

[code]

    int* p=&array[i];
    *p++ += 1; // *p++ = *p++ + 1; に置き換えると「未定義動作」
    // 一度だけ評価が無いと p[i] が変わるのか p[i+1] が変わるのか不明
    
[/code]

[c++](/questions/tagged/c%2b%2b "'c++' のタグが付いた質問を表示") の例

[code]

    int a, debugcount;
    int& func() { ++debugcount; return a; }
    func() += 1; // 同上、一度だけ評価なら debugcount は１増えることが確定
    
[/code]


CC BY-SA 4.0
2021-04-05T00:33:54.447
75086
2021-04-05T00:33:54.447
8589
75061
answer


> x += 1といった累算代入文でxが一度しか評価されないというのはどういう意味ですか？

文字通り x が一度しか評価されないという意味ですが、式 += 1 を意図していると思います。  
OOPer さんが示されている内容と本質的に同じですが、代入文との違いが出る例を提示します。

[7.2.1. 累算代入文 (augmented assignment
statement)](https://docs.python.org/ja/3/reference/simple_stmts.html#grammar-
token-augmented-assignment-stmt)

> augmented_assignment_stmt ::= augtarget augop (expression_list |
> yield_expression)  
>  augtarget ::= identifier | attributeref | subscription | slicing  
>  augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="  
>  | ">>=" | "<<=" | "&=" | "^=" | "|="

augtarget には subscription があり、添字表記式も対象になりますので lst をリストオブジェクトとして  
lst[n] += 1  
も対象です。  
[6.3.2. 添字表記
(subscription)](https://docs.python.org/ja/3/reference/expressions.html#subscriptions)

> subscription ::= primary "[" expression_list "]"  
>  プライマリがシーケンスであれば、式リストの評価結果は整数またはスライス (以下の節で論じます) でなければなりません。

と説明されているのでリストの場合は n に整数を返す式を指定できます。  
そのため

[code]

    lst = [1, 3, 5]
    it = iter(range(5))
    lst[next(it)] += 1  # lst[0] += 1
    print(lst)          # [2, 3, 5]
    
[/code]

とした場合 lst[next(it)] は一度しか評価されないため lst［0］ += 1 となりますが

[code]

    lst = [1, 3, 5]
    it = iter(range(5))
    lst[next(it)] = lst[next(it)] + 1  # lst[1] = lst[0] + 1
    print(lst)                         # [1, 2, 5]
    
[/code]

とした場合、[6.16.
評価順序](https://docs.python.org/ja/3/reference/expressions.html#evaluation-
order) にしたがって、先に右辺の lst[next(it)] が評価され lst［0］ となり、左辺の lst[next(it)]
は後で評価されるため lst［1］ となり違いが出てきます。


CC BY-SA 4.0
2021-04-07T05:42:59.223
75134
2021-04-07T05:42:59.223
41756
75061
answer