bがリストの場合、b + =(4、)が機能し、b = b +(4、)が機能しないのはなぜですか?


77

私たちが取っb = [1,2,3]てやってみたら:b+=(4,)

それはを返しますがb = [1,2,3,4]、実行しようとしてb = b + (4,)も機能しません。

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

私は期待b+=(4,)は、リストやタプルを追加することはできませんとして失敗し、それが働きました。b = b + (4,)同じ結果が得られることを期待して試してみましたが、うまくいきませんでした。


4
ここで答えが見つかると思います
jochen


最初私はこれを誤解し、広すぎると閉じようとした後、撤回しました。それから、それは重複している必要があると思いましたが、投票をやり直すことができなかっただけでなく、私はそれらのような他の答えを見つけようとして髪を引き出しました。:/
Karl Knechtel

回答:


70

「なぜ」の質問の問題は、通常、それらが複数の異なることを意味する可能性があることです。私はあなたが考えているかもしれないと思うそれぞれに答えようとします。

「なぜそれが異なる動作をすることが可能であるのですか?」これは例えばこれで答えられます。基本的に、+=オブジェクトのさまざまなメソッドを使用しようとします:(__iadd__左側でのみチェックされます)、vs __add__および__radd__( "reverse add"、左側にがない場合は右側でチェックされます__add__)のために+

「各バージョンは正確には何をしているのですか?」つまり、list.__iadd__メソッドはと同じことを行いますlist.extend(ただし、言語設計のため、割り当てがまだ残っています)。

これは例えば

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+もちろん、新しいオブジェクトを作成しますが、別のシーケンスから要素をプルしようとする代わりに、明示的に別のリストが必要です。

「なぜこれが+ =に役立つのですか?より効率的です。extendメソッドは新しいオブジェクトを作成する必要がありません。もちろん、これは時々(上記のように)驚くべき効果があり、一般にPythonは実際には効率に関するものではありません、しかしこれらの決定はずっと前に行われた。

「+を含むリストやタプルの追加を許可しない理由は何ですか?」こちらをご覧ください(ありがとう、@ splash58); 1つのアイデアは、(タプル+リスト)は(リスト+​​タプル)と同じ型を生成する必要があり、結果がどの型であるかが明確でないことです。のタイプを明らかに変更すべきではない+=ため、この問題はありません。a += ba


2
おお、まさしく。また、リストではを使用しないため|、私の例を少し台無しにしています。私は明確に例を考える場合は、後で私はそれを交換します。
カールKnechtel

1
|セットのBtw は通勤演算子ですが+、リストの場合はそうではありません。そのため、型のあいまいさに関する議論は特に強いとは思いません。オペレーターは通勤しないので、なぜ型に同じものが必要なのですか?結果がlhsの型であることに同意するだけです。一方、を制限することlist + iteratorで、開発者は意図をより明確にすることが推奨されます。a拡張されたものを含む新しいリストを作成したい場合は、bすでにこれを行う方法がありますnew = a.copy(); new += b。もう1行ですが、非常に明確です。
a_guest

効率とa += bは異なる動作をする理由a = a + b。これは、グイドは、選択した行動とみなされることを実際だより少ない混乱を。関数をa引数としてリストを受け取り、それを行うことを想像してくださいa += [1, 2, 3]。この構文は確かに、新しいリストを作成するのではなく、リストを適切に変更しているように見えるため、期待される結果に関するほとんどの人の直感に従って動作するように決定されました。ただし、このメカニズムはints などの不変の型でも機能する必要があり、現在の設計に至りました。
Sven Marnach

個人的には、Rubyのように単にの省略形を作成するよりも、実際にはデザインがより混乱していると思いますが、どうやってそこに到達したかは理解できます。a += ba = a + b
Sven Marnach

21

それらは同等ではありません:

b += (4,)

の省略形です:

b.extend((4,))

一方+、リストを連結するので、

b = b + (4,)

あなたはタプルをリストに連結しようとしています


14

これを行うと:

b += (4,)

これに変換されます:

b.__iadd__((4,)) 

内部的にはと呼ばれb.extend((4,))extendイテレータを受け入れます。これが機能する理由です。

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

しかし、これを行うと:

b = b + (4,)

これに変換されます:

b = b.__add__((4,)) 

リストオブジェクトのみを受け入れます。


4

公式ドキュメントから、可変シーケンスタイプの両方:

s += t
s.extend(t)

次のように定義されます:

sの内容で拡張t

これは、次のように定義されているのとは異なります。

s = s + t    # not equivalent in Python!

これは、どのシーケンスタイプでも機能することを意味しますtは、例のようなタプルを含め、します。

ただし、範囲とジェネレーターでも機能します。たとえば、次のこともできます。

s += range(3)

3

のような「拡張された」代入演算子+=は、2000年10月にリリースされたPython 2.0で導入されました。設計と根拠については、PEP 203で説明されています。これらのオペレーターの宣言された目標の1つは、インプレース操作のサポートでした。書き込み

a = [1, 2, 3]
a += [4, 5, 6]

そのa 場でリストを更新することになっています。これは、リストへの他の参照がある場合に重要ですa。たとえば、aが関数の引数として受け取られた場合です。

ただし、整数や文字列を含む多くのPythonタイプは不変であるため、操作が常に行われるとは限りません。たとえばi += 1、整数のi場合、適切に操作できない可能性があります。

要約すると、拡張代入演算子は可能な場合は適切に機能し、それ以外の場合は新しいオブジェクトを作成することになっています。これらの設計目標を容易にするために、式x += yは次のように動作するように指定されました。

  • x.__iadd__が定義されている場合、x.__iadd__(y)評価されます。
  • それ以外の場合、x.__add__実装されている場合x.__add__(y)いる評価されます。
  • それ以外の場合、y.__radd__実装されている場合y.__radd__(x)いる評価されます。
  • そうでない場合はエラーが発生します。

このプロセスで取得された最初の結果xは、(その結果がNotImplementedシングルトンで場合。この場合、ルックアップは次のステップに進みます)。

このプロセスにより、インプレース変更をサポートする型でを実装でき__iadd__()ます。インプレース変更をサポートしない型は、新しいマジックメソッドを追加する必要がありません。Pythonは自動的に本質的にフォールバックするためです。x = x + y

それでは、最後に実際の質問に行きましょう。なぜ拡張代入演算子を使ってタプルをリストに追加できるのでしょうか。メモリから、これの歴史はおおよそ次のようでした:このlist.__iadd__()メソッドはlist.extend()、Python 2.0の既存のメソッドを単に呼び出すように実装されました。Python 2.1でイテレータが導入されたとき、list.extend()メソッドは任意のイテレータを受け入れるように更新されました。これらの変更の最終結果はmy_list += my_tuple、Python 2.1から機能しました。のlist.__add__()ただし、メソッドは、任意の反復子を右側の引数としてサポートすることは想定されていませんでした。これは、強く型付けされた言語には不適切と見なされていました。

個人的には、拡張演算子の実装はPythonでは少し複雑になりすぎたと思います。それは多くの驚くべき副作用を持っています、例えばこのコード:

t = ([42], [43])
t[0] += [44]

2行目は、発生させTypeError: 'tuple' object does not support item assignmentしかし、動作が正常とにかく行われる - tとなり([42, 44], [43])エラーが発生行を実行した後。


ブラボー!PEPへの参照があると特に便利です。タプル内リストの動作に関する以前のSOの質問に、もう一方の端にリンクを追加しました。2.3以前のPythonがどのようなものであったかを振り返ると、今日と比べて実際には使用できないように見えます(そして、非常に古いMacで何か1.5を実行しようとして失敗したという漠然とした記憶があります)
カールクネクテル

2

ほとんどの人は、X + = YがX = X + Yと同等であることを期待します。実際、Mark LutzによるPython Pocket Reference(4th ed)は57ページで次のように述べています。 X + = Y "。しかし、Pythonを指定した人々はそれらを同等のものにしませんでした。おそらくそれは間違いでした。Pythonが使用されている限り、イライラしたプログラマーは何時間もデバッグに時間を費やすことになりますが、今ではPythonがそのままです。Xが可変シーケンスタイプの場合、X + = YはX.extend(Y)と同等であり、X = X + Yと同等ではありません。


>おそらくそれは間違いでした。Pythonが使用されている限り、イライラしたプログラマーは何時間もデバッグに時間を費やすことになります<-このために実際に苦しみましたか?あなたは経験から話しているようです。あなたの話を聞いてください。
Veky

1

ここで説明しているように、メソッドをarray実装しない場合、これは単なる省略形ですが、明らかにそうではないため、メソッドを実装します。どうやらメソッドの実装は次のようなものです:__iadd__b+=(4,)b = b + (4,)array__iadd____iadd__

def __iadd__(self, x):
    self.extend(x)

ただし、上記のコードは__iadd__メソッドの実際の実装ではないことはわかっていますが、メソッドのようなextendtupple入力を受け入れるものがあると想定して受け入れることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.