質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

2回答

711閲覧

pythonのリストの挙動について

fu_3823

総合スコア81

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

1グッド

3クリップ

投稿2020/08/02 14:14

Pythonで以下のようなリストをつくりました。

Python

1a =[[0,0,0] for n in range(4)] 2# a:[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] 3 4b = [[0] * 3] * 4 5# b:[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

その後の処理とその結果が次のようになりました。

Python

1a[1][1] = 5 2# [[0, 0, 0], [0, 5, 0], [0, 0, 0], [0, 0, 0]] 3 4b[1][1] = 5 5# [[0, 5, 0], [0, 5, 0], [0, 5, 0], [0, 5, 0]]

なぜ、こんなことになるのでしょう。
リストbの場合も、リストaのような挙動を期待してしまうのですが、そうならない理由がわかりません。

tiitoi👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

a =[[0,0,0] for n in range(4)]

を中間コードにデコンパイルすると、

1 0 BUILD_LIST 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 14 (to 20) 6 STORE_FAST 1 (n) 8 LOAD_CONST 0 (0) 10 LOAD_CONST 0 (0) 12 LOAD_CONST 0 (0) 14 BUILD_LIST 3 16 LIST_APPEND 2 18 JUMP_ABSOLUTE 4 >> 20 RETURN_VALUE

というようにBUILD_LISTがループによって計4回呼び出されています。
つまり、新しいリストが4つ作成されています。
一方、

b = [[0] * 3] * 4

を中間コードにデコンパイルすると

1 0 LOAD_CONST 0 (0) 2 BUILD_LIST 1 4 LOAD_CONST 1 (3) 6 BINARY_MULTIPLY 8 BUILD_LIST 1 10 LOAD_CONST 2 (4) 12 BINARY_MULTIPLY 14 STORE_NAME 0 (b) 16 LOAD_CONST 3 (None) 18 RETURN_VALUE

とBUILD_LIST は、[0]の作成と[0,0,0]の作成の2回しか呼び出されておらず、1回目になかった「BINARY_MULTIPLY」という命令が見受けられます。つまりキモは多分この「BINARY_MULTIPLY」でしょう。

pythonコンパイラのソースを探すと、ここを経由してここで定義されているようです。
ここからの中身の詳細は割愛しますが、引数の種類によって、PyNumberMethods.nb_multiply または PySequenceMethods.sq_repeat のいずれかが呼ばれるらしいです。

そして、リストオブジェクトの場合、PySequenceMethods.sq_repeatが呼ばれ、最終的にlist_repeat(PyListObject *a, Py_ssize_t n)(該当箇所)
が呼ばれているようです。
この中では、結局、与えられたリストへの同じ参照値が回数分だけコピーされています。

以上を総合すると、
1番目のリスト作成方法(a =[[0,0,0] for n in range(4)])では、異なるリストオブジェクトを4つ作成している(すなわちそれぞれのリストオブジェクトの参照値も異なる)のに対して、
2番目の「*」演算子を使ったリストの作成([[0] * 3] * 4)では、
左に置かれた[0] * 3によって作成された1つのリストオブジェクトに対する同一の参照値を繰り返してコピーしているだけなので、
b[1][1]に値を代入したときの結果が異なるものと推測されます。

下記コードのように、各場合の要素の参照値を検証すると、1番目の場合は参照値が異なりますが、2番目の場合は参照値が4つとも同一であることが確認できます。

a =[[0,0,0] for n in range(4)] print(id(a[0])) print(id(a[1])) print(id(a[2])) print(id(a[3])) # 出力結果 >>> 2458168546376 >>> 2458176280200 >>> 2458176279624 >>> 2458169098184 b = [[0] * 3] * 4 print(id(b[0])) print(id(b[1])) print(id(b[2])) print(id(b[3])) # 出力結果 >>> 1720977937480 >>> 1720977937480 >>> 1720977937480 >>> 1720977937480

投稿2020/08/02 15:36

編集2020/08/02 16:44
patapi

総合スコア660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fu_3823

2020/08/03 00:50

非常にわかりやすい説明ありがとうございました。
guest

0

投稿2020/08/02 21:47

編集2020/08/03 05:45
shiracamus

総合スコア5406

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問