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

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

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

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

959閲覧

(Python)for文でのNumpy配列のelements変更について

hiroshiu12

総合スコア12

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

1グッド

1クリップ

投稿2018/01/04 02:16

編集2018/01/04 02:22

Pythonのnumpy配列のfor文における値変更について自力では解決できない疑問がありますので、質問させて頂きます。
###疑問点
例えば、以下のようなnumpyの2次元のリストがあったとします。

python

1In [1]: import numpy as np 2 3In [2]: twodim_array = np.arange(9).reshape(3,3) 4 5In [3]: twodim_array 6Out[3]: 7array([[0, 1, 2], 8 [3, 4, 5], 9 [6, 7, 8]])

これをfor文で回して、中のリストの値を変更しようとすると、以下のように元のリスト(twodim_array)の内容が変更される事が確認できます。

python

1In [4]: for array in twodim_array: 2 ...: array[1] = 5 3 ...: 4 5In [5]: twodim_array 6Out[5]: 7array([[0, 5, 2], 8 [3, 5, 5], 9 [6, 5, 8]]) 10 11In [6]:

しかし、例えば以下のように要素がintの配列の中身を変えようとしても、元の配列の内容は変更されません。

python

1In [7]: onedim_array = np.arange(3) 2 3In [8]: onedim_array 4Out[8]: array([0, 1, 2]) 5 6In [9]: for int_val in onedim_array: 7 ...: int_val = 5 8 ...: 9 10In [10]: onedim_array 11Out[10]: array([0, 1, 2]) 12 13In [11]:

この2つの違いは何でしょうか。
前者は、listの中身がmutable,後者はimmutableである事かと思い、以下のように確認してみましたが、謎が深まるばかりで理解できませんでした。

###考察
for文によって参照されているオブジェクトが元のオブジェクトかどうか??

python

1In [11]: for array in twodim_array: 2 ...: print(id(array)) 3 ...: 44674307536 54674308736 64674307536 7 8In [12]: for array in twodim_array: 9 ...: print(id(array)) 10 ...: 114673935360 124674307536 134673935360 14 15In [13]: for array in twodim_array: 16 ...: print(id(array)) 17 ...: 184674557392 194673935360 204674557392 21 22In [14]: 23 24In [14]: id(twodim_array[0]) 25Out[14]: 4673766544 26 27In [15]: id(twodim_array[1]) 28Out[15]: 4673936160 29 30In [16]: id(twodim_array[2]) 31Out[16]: 4585491872 32 33In [17]:

要素がnumpyであるnumpy配列(2次元)のfor文によって参照されるオブジェクトは、元のオブジェクトと違うようですし、しかも、毎回別のものが参照されている。
そして、さらに分からないのが、for文の中で0番目と2番目のオブジェクトIDが同じ番号になっている!
以下のように、intを要素にもつnumpy配列でも似たような結果となりました。

python

1In [17]: for int_val in onedim_array: 2 ...: print(id(int_val)) 3 ...: 44671588584 54671588608 64671588584 7 8In [18]: for int_val in onedim_array: 9 ...: print(id(int_val)) 10 ...: 114671588632 124671588584 134671588632 14 15In [19]: for int_val in onedim_array: 16 ...: print(id(int_val)) 17 ...: 184671588656 194671588632 204671588656 21 22In [20]: id(onedim_array[0]) 23Out[20]: 4671588680 24 25In [21]: id(onedim_array[1]) 26Out[21]: 4671588704 27 28In [22]: id(onedim_array[2]) 29Out[22]: 4671588728 30 31In [23]:

まとめ

以下の点を含めて、説明できる方がいらっしゃいましたら、教えていただけないでしょうか。。。

・for文でのオブジェクトidと[int]で参照した時のオブジェクトidが違うのに、何故、元のリストの値を更新する事ができるのか。。
・for文の中で0番目と2番目のオブジェクトidが等しいのは何故か。
・intを要素に持つnumpy配列は、for文によって元のnumpy配列の値を変更する事はできないが、numpyを要素に持つnumpy配列は、for文によって元の配列を変更できるのは何故か。。

よろしくお願いいたします。

umyu👍を押しています

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

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

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

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

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

guest

回答3

0

ベストアンサー

numpy.arrayの中身を取り出そうとすると必ず決まったメモリに割り当てられるみたいですね。
0番目と2番目が等しいというより、その取り出し方だと、0,2,4,6,8,...が同じidになります。
ついでに

python

1a = np.arange(10) 2id(a[0]) 3id(a[0}) 4id(a[0}) 5b = np.arange(10) 6id(b[0}) 7id(a[0})

としても同じidが交互に現れます。
numpyが作業用に持つスタックメモリに乗せてから処理しているように思いますが、コードを直接読んだわけではないので確証はありませんが。
https://stackoverflow.com/questions/35232406/why-is-a-for-over-a-python-list-faster-than-over-a-numpy-array


値の更新についてはmutable、immutableに関係しています。
取り出した要素がimmutableの場合、等号はその要素を示していた変数の指す場所をかえます。
これに対してmutableの要素の場合、等号はmutableの要素の中身の指す場所をかえます。

python

1import numpy as np 2 3a = np.ones((4,4)) 4print(a) 5for v in a: 6 v = np.zeros(4) 7print(a) 8for v in a: 9 v[:] = np.zeros(4) 10print(a)

投稿2018/01/04 03:06

mkgrei

総合スコア8560

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

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

hiroshiu12

2018/01/04 07:09

ご回答ありがとうございます。 私の疑問の本質的な部分をズバリ回答していて、とても感謝していると共に、びっくりしています。 mkgreiさんの回答をみた後、普通のlistで同じ挙動を確認してみたところ、確かに、取り出し方に関わらず、idはいつも一定のものでした。 また、2点質問させて頂いてもよろしいでしょうか。 ・回答の最後にあるPythonコードは、2個目のprintでは値が変更されないが、3個目では変更されました。これは、mutableかimmutableかというよりは、KSwordOfHasteさんの回答にある、'変数への単純な代入文(A)と指標付きの代入文(B)'の違いという事でしょうか。 ・id()で取得できるのは、変数が指すオブジェクトの実際のメモリ上のアドレスだったりするのでしょうか。ご存知でしたら、教えていただけますか。
mkgrei

2018/01/04 15:29 編集

代入というのが多義的なのにシンプルな文法を持つ(?)、オーバーロードがたくさんあるPythonではいろいろと想定外の振る舞いをしますね。 以下ご質問に直接答えているところもあれば、関連トピックで雑多な話も含みます。 まず前半について、 まさにおっしゃる通りで、変数のアクセサメソッドを使うと等号の左にあるか右にあるかで適切な対応メソッドが呼び出されます。 listもnp.arrayも、'__setitem__'、'__getitem__'という特別なメソッドが呼ばれます。 これが現象として、以下のような振る舞いを引き起こします。 変数自体に等号を使うと、その名前の変数の指す先をかえてしまいます。 これは名付けの問題であって、変数が指しているものの実体を操作しているわけではないのです。 この時'__setitem__'は呼ばれません。 これは変数が指しているものがmutableであってもimmutableであっても同じです。 変数の要素に等号を使うとmutableの場合その要素の中身をかえます。 少々私の比較が紛らわしかったのですが、例えばtupleの場合immutableで要素に対して代入を使うとエラーになります。 実はtupleには'__setitem__'が実装されていません。 他にimmutableの振る舞い関連といえば有名なのが、 print(id(6)) a=6 print(id(a)) が同じidになることだと思います。 またよくある謎なテクニックとして、listに対して、a[:]をするとシャローコピーされます。 その場合aの中身を消しながら安全にイテレートできるようになります。 idは抽象化されたPythonのインタプリタで扱うものだと認識しています。 仮想メモリのマッピングですらないのだと思います。 https://stackoverflow.com/questions/121396/accessing-object-memory-address リンク先に振る舞いについての話があります。
hiroshiu12

2018/01/04 08:40

なるほど。'変数名[]'と記載した時は、等号の右にあるか左にあるかで、どちらのアクセサメソッド(__setitem__か__geitem__か)が呼ばれるか決まるのですね。勉強になります。 ところで、idについて'仮想メモリのマッピングですらないのだと思います。'とありますが、以下のmanualには、id()のすぐ下に、"CPython implementation detail: This is the address of the object in memory."とあります。 https://docs.python.org/2/library/functions.html#id これを見ると、id()で取得する値は、メモリアドレスそのもののように読めますが、違うのでしょうか?
mkgrei

2018/01/04 09:13

おっしゃることは正しいです。 ただ、考える際に3つほど思うことがあったので、上記のようなコメントになりました。 まず、CPythonに限ってはそうですが、他にもインタプリタの実装もそうだとは限りません。 Pythonという言語の仕様というより、よく使われるCPythonというインタプリタの仕様になります。 次に、並列させたときに混乱が生じます。 multi-threadingかmulti-processingで振る舞いが異なるのです。 https://stackoverflow.com/questions/33662292/why-do-new-objects-in-multiprocessing-have-the-same-id 最後に、CやFortranのような言語における数や配列などとはだいぶ異なる振る舞いをします。 Pythonのlistはlinked-listという構造体というせいもありますが。 また数字もなかなかに不思議な振る舞いをします。 isによる比較はNoneにとどめたほうが安全といったことにもつながります。 https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers https://stackoverflow.com/questions/3917574/how-is-pythons-list-implemented 以上まとめると、 一般的に使われるCPythonインタプリタを使い、並列計算をせずに、Pythonの構造体についてよく理解しているのであれば、idはメモリアドレスとして考えても問題ないということになります。
yosider

2018/01/04 14:09

3つ上のmkgreiさんの回答について部分的な指摘になりますが,list aにたいしてa[:]はdeep copyではなくshallow copy(浅いコピー)ではないでしょうか.
mkgrei

2018/01/04 14:35

以下のコードを実行してみるとわかります。 a = list(range(3)) b = a[:] a[0] = 9 print(b) # [0, 1, 2] a = list(range(3)) b = a a[0] = 9 print(b) # [9, 1, 2]
yosider

2018/01/04 14:46

申し訳ありません,deep copyとshallow copyの概念を逆に覚えておりました. 大変失礼いたしました.
LouiS0616

2018/01/04 15:15 編集

横から失礼します。 a[:]は各要素のidが保持されるので、リファレンスではシャロ―コピーと表現されています。 もちろんmkgreiさんの書いたコードは間違いないですので、単に単語の用法に関する問題ですが。 --- a = [[i] for i in range(3)] b = a[:] b[0].append(1) print(a) # ディープコピーなら[[0], [1], [2]]と出力されてほしいところ
mkgrei

2018/01/04 15:28

LouiS0616さん、コメントをありがとうございます。 やってしまいました。 中身がmutableの時にうまくコピーされるのがdeep copyでした。 yosiderさん、申し訳ありません。 過去のディープコピーのくだりを修正しておきます。
yosider

2018/01/05 08:07

いえいえ,私に関しては単に勉強不足でした. 確認になりますが,list a に対して a[:] は,  aの各要素のシャローコピーを要素にもつ新しいlist であり,リファレンスではこれ自体をシャローコピーと呼んでいるということでしょうか.
mkgrei

2018/01/05 08:13

リファレンスの該当部分は以下のものを想定していますか? >浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します。 >深いコピー (deep copy) は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。
yosider

2018/01/05 08:32

いえ,LouiS0616さんの「a[:]は各要素のidが保持されるので、リファレンスではシャロ―コピーと表現されています。」というご発言を受けてのものであり,私が実際にリファレンスを確認したわけではありません. いままで私はオブジェクトxに対して y = x として得られるyのことをshallow copyというのだと勘違いしておりましたが,お示しのリファレンスによれば,a[:]はまさにshallow copyに当たりますね. 勉強になります.ありがとうございます.
mkgrei

2018/01/05 09:10

段階的にコピーのように振る舞うものが複数あるので紛らわしいですね。 y = xだとyとxは同じものを指しているのでコピーではないのです。 そして配列等に対してb = a[:]とするとbはaとは別物として作れるのでコピーになります。でもその中身はできるだけaと同じものになるようにコピーの時間とメモリ消費を抑えます。 ただ、それだと中身が更に他のものを参照した時に、その先のものの変更が副作用として影響を与えてきます。 そこで、中身をすべて辿ってコピーを作るディープコピーがたまに必要になるのです。 リストになんでも入れられるPythonの宿命ですね。
hiroshiu12

2018/01/20 08:16

mkgreiさん、返信が非常に遅くなってすみません。 私の最初の質問に対して、本質を付く回答をいただけたのはmkgreiさんですので、ベストアンサーとさせていただきます。
guest

0

本件はnumpy.arrayとか要素の型といった話とは独立した別の点がポイントになっているように思います。(書いているうちにnamnium1125さんの回答がつき、内容に重複がありますがご容赦を)


変数への単純な代入文(A)と指標付きの代入文(B)では意味が異なります。

(A) var = expression
(B) var[index_object] = expression

(A)は変数の箱に入っていた古い値を箱から放り出し(曖昧な言い方で申し訳ないですが)、右辺の値を箱に入れるようなイメージになります。

一方(B)は変数varの箱に入っていたオブジェクトに対して

object.__setitem__(self, key, value)

を呼び出すという意味になるようですが、listやnumpy.arrayのような「コンテナ」はこのメソッドで内部の状態(要するに要素の値)を変更するようになっており、普通keyに対応する要素を記録する場所(変数の箱のようなもの)に入っていた値を箱から放り出し、valueの値を箱に入れるという意味合いになります。

つまりnumpy.arrayが特別というわけではなく、コレクション一般でも同様の意味合いになります。


ところで考察の方ですが

要素がnumpyであるnumpy配列(2次元)のfor文によって参照されるオブジェクトは、元のオブジェクトと違うようですし、しかも、毎回別のものが参照されている。

そして、さらに分からないのが、for文の中で0番目と2番目のオブジェクトIDが同じ番号になっている!
以下のように、intを要素にもつnumpy配列でも似たような結果となりました。

python

1a = [1, 1, 2] 2print(id(a)) 3for i in a: 4 print(id(i))

のようにしたとき、4つの値が印字されますがそれぞれ印字対象になっているのは

印字順印字対象のオブジェクト
#1変数aの箱に入っているlistオブジェクト
#2リストの先頭要素(つまり1)
#3リストの2番目の要素(つまり1)
#4リストの3番目の要素(つまり2)

です。ゆえに#2, #3が同一のidとなったり、#1, #4がそれと異なるのは自然に思えます。上記はPythonの世界では次のようなイメージになっているのですが、多分どこかで勘違いされていると思います。ひょっとしたら「変数」、「変数に入っている値(=オブジェクト)」というような点の理解に何か齟齬があるような気がします。

text

1変数aの箱 2 +-------+ listオブジェクト(#1) 3 | --------------> +----------+ 4 +-------+ | -----------+--> intオブジェクト1(#2,#3) 5 +----------+ | 6 | -----------+ 7 +----------+ 8 | --------------> intオブジェクト2(#4) 9 +----------+

投稿2018/01/04 03:08

KSwordOfHaste

総合スコア18394

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

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

KSwordOfHaste

2018/01/04 03:10

考察の方ですが、mkgreiさんの回答が的を得ているようです。自分の回答は質問者さんが感じておられる疑問を外しているような気がしました。失礼!
mkgrei

2018/01/04 03:36

listとnumpy.arrayで実装が異なることからくる混乱の気がします。 listのほうが実装は自然な気がします。 中身のidが、それがどのように取り出されたのかにかかわらず一定になりますので。 Pythonのインタプリタからみて、中身も含めてidを持っているようです。 numpy.arrayは高速化、他言語実装のせいか、不思議な挙動をしますね。 Pythonのオブジェクトとして取り出された時に初めてidを与えられるようなのです。 そしてインタプリタからの参照カウントが0になると開放されているようです。 変数スコープを正しく与えることで両者の使うメモリはあまり変わりませんが、 変なことをすることによって、numpy.arrayで「メモリリーク」のようなことを発生させることができそうです。
KSwordOfHaste

2018/01/04 03:43

> listとnumpy.arrayで実装が異なることからくる混乱の気がします。 はい、mkgreiさん回答を拝見して「そっちだったか」と気づきました。 numpyはデータの実体と配列としてのビューを別個に持っているようで、複数のインスタンスで実体を共有するような工夫がされていますよね・・・ a = np.array([1,2,3,4]) b = a.reshap((2,2)) a[1]=22 なんてことを最初のころやってみてbの値も変化することからリファレンスを見て「共有されるよ」みたいなことが書いてあったので「うーむなるほど」と感心したことを思い出しました。あまりそういう点を意識するようなまともなコードを書いてないので普段意識してませんでしたw;
hiroshiu12

2018/01/04 07:15

KSwordOfHasteさん、ありがとうございます。 '変数への単純な代入文(A)と指標付きの代入文(B)'の違いはとても参考になりました。また、ここでのmkgreiさんとのやりとりによって、理解が深まりました。ありがとうございます。 1点質問ですが、'numpyはデータの実体と配列としてのビューを別個に持っているよう'とは、c言語で保持している実体と、それを参照しているpythonオブジェクトを指しているでしょうか??pythonオブジェクトとしての複数のインスタンスが同一の実態(C言語)を参照しているというような意味だと理解しましたが、あっていますか?
KSwordOfHaste

2018/01/04 07:36

そういう意味合いで言いました。ただし実体の実装がPythonレベルなのかPythonの内部実装なのかまで言及しているつもりはありません。データの実体と次元などの属性を別々に表現して、異なるオブジェクトで実体を共有し得るというのがポイントで、そのような実装は例えばPythonのユーザー定義クラスでも可能だと思います。
hiroshiu12

2018/01/04 08:54

なるほど、KSwordOfHasteさんが記載した、 >a = np.array([1,2,3,4]) >b = a.reshap((2,2)) >a[1]=22 の現象は、reshapメソッドがcopyではなく、viewオブジェクトを返す事に起因するとう事ですね??
KSwordOfHaste

2018/01/04 09:09

numpy.array自体が実体を共有しているということだと思いますが、mkgreiさんがおっしゃるように配列の一部の参照、たとえばa[0]のようなものもビュー的なものになっているのでしょう。 おもしろいですね。id(a[0])を少し印字してみると、まったく同じ式を書いても、idの値が変化したりしなかったりしますね。単なるビューなのでキャッシュされて再利用されたりしているのかなぁと想像しました(単なる想像です)
mkgrei

2018/01/04 09:39

pythonのidはかなりトリッキーな振る舞いをするのですよね。 https://stackoverflow.com/questions/3877230/why-does-id-id-and-id-id-in-cpython ちなみにviewのアドレスは特別な場所からスタートするようになっているようで、 途中で新しい配列を作ったりしてもviewでなければ同じところからスタートします。 スライスをする場合ときと要素取り出しでidが変わったりします。 というより、オブジェクトの種類別にスタートするidが異なるようですね。 これはPythonの仕様かもしれません。 GCで回収しなければidがずれていくことを期待しましたが、そんなことはありませんでした。 本当は物理メモリはランダムアクセスなので別のidになるべきだと思われるのですが、仮想上は同じidに落ち着くような振る舞いをするのです。 もしかしたらファイルシステムのインデックスのようなものなのかもしれません。
KSwordOfHaste

2018/01/04 10:19

本題から外れてしまいますが、PythonはCとの連携を重視するためガベージコンパクションを行わない方式を用いているという記事を読んだことがあります。リファレンスには「CPython 実装の詳細: This is the address of the object in memory.」とあるのでアドレスなのだろうと思いました。
mkgrei

2018/01/04 12:47

「CPython 実装の詳細: This is the address of the object in memory.」 と言うのはおっしゃるとおりで、CPythonは実装としてアドレスを返すのですが、 objectというのが必ずしも直感的ではないのがPythonの実装の嫌なところかと思われます。 idはユニークになることを仕様上求められています。 そこで確かにメモリのアドレスを与えればユニークになりそうということで実装されているわけですが、 以下のコードを実行してみるとまか不思議な振る舞いを見ることができます。 そしてなんとその不思議な振る舞いは環境に依存するというおまけ付きで、デバッグツールの1つとして有効な時もありますが、あまり信用してはいけない仕様となっています。 https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers 本来257でも同じ振る舞いをするはずなのですが、私の環境(Mac/Python3.x)では再現できないという… そしてUbuntu環境だとPython2.xは以下のコードも再現できない一方で、Python3.xだと再現できるという… つまり、メモリのアドレスを基本的に返すが、返す結果に変なことがあっても気にしてはいけないということなのです。 なんといいますか、間違ってはいないけど、期待通りの振る舞いをする保証もなければ、環境に依存もするという意味で、あまりメモリアドレスとして考えすぎるのはよくないのかもしれません。 確かにリストの中身がいつの間にか変わっている時にidをチェックして変な参照がないかはたまにチェックしますが。 a = -6 b = a print('a {0}, b {1}'.format(a, b)) #a -6, b -6 print('a is b {0}'.format(a is b)) #a is b True print('a {0}, b {1}\n'.format(id(a), id(b))) a = -6 print('a {0}, b {1}'.format(a, b)) #a -6, b -6 print('a is b {0}'.format(a is b)) #a is b False print('a {0}, b {1}'.format(id(a), id(b))) print('a {0}\n'.format(id(a))) print('a {0}'.format(a)) #a -6 print('a is -6 {0}'.format(a is -6)) #a is -6 False print('a {0}\n'.format(id(a)))
KSwordOfHaste

2018/01/04 14:11

こうした例はJavaのprimitiveをboxingした結果生成されるオブジェクトやLispのbignumとかinternされていないstringが「値の同一性は保証するが同一のオブジェクトであることは必ずしも保証しない」という例があることを考えるとPythonが特別奇妙という訳でもない気がします。たまたまIDLEで以下のようになりましたが・・・ >>> 1 is 1 #True >>> -5 is -5 #True >>> -2123928192 is -2123928192 #False これLispやjavaでも同様のことが起きると思います。Lispで(eq 1e10 1e10)は多分NILになることが普通でしょうし、Javaでもnew String("abc")==new String("abc")は多分偽になるでしょうから!
KSwordOfHaste

2018/01/04 14:31

ところで、本質問に関連して... a=numpy.array([1,2,3]); a[1] is a[1] # False この結果は「ビューを作っているからFalse」と最初思ったのですがmkgreiさんのコメントを拝見してふと気づきました。type(np.array([1,2,3])[1]) => <class 'numpy.int32'>なんですね... これviewではなく単にnumpyでの値型の一種なだけで-6 is -6がFalseになることがあると同じ理由で「違う実体になることがある」ということなのだろうかと思えてきました。どうもそっちの方がありそうな気が・・・
mkgrei

2018/01/04 15:12

たしかにそうですね。 余計なことを気にしすぎていたのかもしれません。 失礼しました。 Pythonが参照渡しと言われている割に振る舞いがC言語でのポインタらしくなかったので妙に気になってしまいました。 a=-6 b=a b=-7 print(a) とした時にC言語でbをポインタにするとaは変更されるのに対して、ポインタでない代入であれば変更されないので、無駄に混乱していました。 Pythonでidを使うときは同一性を確かめるときが主だと思っているのですが、数字みたいなものの場合、同一性とは何かについてよく考えないといけない、ということを言いたかったような気がします。 わざわざお付き合い頂きありがとうございました。
mkgrei

2018/01/04 15:52 編集

a=numpy.array([1,2,3]); a[1] is a[1] これはprint文の処理の問題の気がします。 idを2度print文に突っ込むと同じidになるのですが、実はこれは、まず1個目をidして、それをもう書き出しています。 そして、コンマを挟むと1個目のオブジェクトが解放されて、それで空いた同じアドレスにもう一度ビューを作っています。 is文を使うと、まずa[1]のビューを1つ作って、でもこれは評価のために解放できないので、2つめのa[1]は別のアドレスにビューを作っています。 print文中に例外を発生させると、例外が起きる前まで書き出されてぶつ切りになってしまう現象に通ずるものかと思われます。
KSwordOfHaste

2018/01/04 23:14

すみません。numpy.int32のようなものがviewではなく(C#などでいう)値型だと思ったのは a = np.array([1,2,3]) a1 = a[1] a[1] = 22 としたときa1の値が2のままだから。と言うべきでした。idが同じかどうかという例がわかるかったと思います。 意味的にa[0]の結果がスカラーなら値型インスタンスとなりa[1]の結果が配列ならそれはコンテナ(そしてview)になるはずだから当たり前のことを勘違いしていた気がします・・・
mkgrei

2018/01/05 05:03

本当ですね。 numpyでも数字の要素まで行ってしまうとビューではなく、実体が作られるのですね。 a=numpy.array([1,2,3]) print(id(a[1]), id(a[1])) #同じ数字が2つ print(a[1] is a[1]) #False になる理由について考えていました。 同じidを持っているようにみえるのに、is文でFalseを出す不思議についてです。 参照カウンタが0にならない限りスライスしてもアクセスしても新しいidが作られていく実装みたいです。 また、immutableを実体に持つ変数を代入すると参照が外れて追従しないのが、参照渡しと思ってたのにショックという話でした。 a = 0 b = a a = 1 print(b) #0 bはaだと思ったのに、1にならないのか、という誤解でした。
KSwordOfHaste

2018/01/05 05:51 編集

最初の例の方ですが、numpy.int32などは特にインスタンスの共有を気にしていないということのように思えました。karamarimoさんがコメントくださったようにpython自体はintの実体を作るときによく出現するインスタンスをぼこぼこ生成しないよう配慮していますが、numpyの場合はそれが必要ないという判断なのだと思いました。 numpy.arrayの中身のスカラーの値は多くの場合numpyの配列演算にまかせ、一つ一つの値をユーザープログラム内のあちこちの変数に覚えておくようなケースを想定する必要性が低いのだと思います。Pythonは参照カウンタによるGCのため「使われなくなった途端に回収される」のでインスタンスを作って回収することに対する配慮はJava以上に手軽にやっているのだと思います。javaでも新規に生成されたオブジェクトは効率よく回収されますが、Pythonの場合はコンパクションなしのリファレンスカウンターなのでインスタンスを生成して必要なくなったら回収というライフサイクルはC++のコンストラクター・デストラクターで自動回収されるメモリーと同程度の軽さなのだと思います。 --- なんか質問者さんより自分の方が勉強させてもらった気分です・・・
mkgrei

2018/01/05 07:35

pythonはintについて-5~256までは多重に生成しないようにしています。 ただそれも、インタプリタのコンパイル環境次第のようで、それが上記で257などでも私の環境は多重生成しないようになっていました。 (インタラクティブモードだと再現されるので、スクリプトファイルとして実行する際の最適化が入っているせいかもしれません。) numpyの場合、必ず新しいものが作られます。(少なくとも試している限りではですが。) それを利用することによって、リストにどんどん入れてしまうと参照カウントが0にならず、メモリを圧迫するという意味で「メモリリーク」と表現しました。 実体に到達できるという意味で「リーク」はしていないのですが。 PythonのGCについて簡易な読み物として、 http://postd.cc/visualizing-garbage-collection-in-ruby-and-python/ Pythonでは自分でメモリマッピングについて考えてしまうと言語実装的に確定的ではない振る舞いをするので、あまり本質的ではないわかりにくい話をしてしまっていました。 リファレンスにはメモリアドレスと書いてありますし、文字通りに実装もされていますが、ユニークな数字である程度に認識するのが一番良いのかなと思われた次第です。
guest

0

「まとめ」で挙げられている質問には答えてません。

ただ始めの方で気になったことがあったのでそのことについてのみ回答させていただきます。ご了承ください。m(_ _)m

numpy云々関係なく、

python

1arr = [0,1,2] 2 3for n in arr: 4 n = 5

としたところで配列にアクセスしているわけではないですから、値は変わらなくて当然です。

C言語のポインタ等で説明すればいいのでしょうか?…とりあえず普通に説明してみます。

python

1twodim_arr = [[i for i in range(j*3,j*3+3)] for j in range(3)] 2 3for arr in twodim_arr: 4 arr[1] = 5

ここでarrに入るのは配列を指すアドレスと考えられます。

ということはこのアドレスが指す配列の値を変更すれば当然、twodim_arrが持つアドレスが指す配列は同じものですから、結果的にtwodim_arrの持つ配列の中の値が変わることになります。

一方最初の例ではnに入るのはただの整数値ですから、これを変えたところで配列にはなんの影響もありません。

numpyもこの仕様に合わせているのではないでしょうか?

○蛇足的な何か

全ての要素を5にするような操作をしたければ普通に

python

1arr = [5,5,5]

とするか、要素数不明なら

python

1arr = [5 for _ in arr]

と書くと良いでしょう。

投稿2018/01/04 02:53

編集2018/01/04 02:58
namnium1125

総合スコア2043

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

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

hiroshiu12

2018/01/04 06:57

namnium1125さん、ありがとうございます。 namnium1125さんのご指摘通り、id()で取得できるのは、'変数がさしているオブジェクトのIDである'という基本的な事が怪しい書き方をしておりました。ご指摘ありがとうありがとうございます。 理解していたつもりだったのですが、mkgreiさんの回答にあるように、numpyはpythonオブジェクトとして取り出された時に初めてオブジェクトIDが割り振られているようで、混乱しておりました。 また、"蛇足的な何か”もありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問