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

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

ただいまの
回答率

91.06%

  • Python 3.x

    3705questions

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

  • NumPy

    292questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 292

hiroshiu12

score 4

Pythonのnumpy配列のfor文における値変更について自力では解決できない疑問がありますので、質問させて頂きます。

疑問点

例えば、以下のようなnumpyの2次元のリストがあったとします。

In [1]: import numpy as np

In [2]: twodim_array = np.arange(9).reshape(3,3)

In [3]: twodim_array
Out[3]: 
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])


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

In [4]: for array in twodim_array:
   ...:     array[1] = 5
   ...:     

In [5]: twodim_array
Out[5]: 
array([[0, 5, 2],
       [3, 5, 5],
       [6, 5, 8]])

In [6]: 

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

In [7]: onedim_array = np.arange(3)

In [8]: onedim_array
Out[8]: array([0, 1, 2])

In [9]: for int_val in onedim_array:
   ...:     int_val = 5
   ...:     

In [10]: onedim_array
Out[10]: array([0, 1, 2])

In [11]: 


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

考察

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

In [11]: for array in twodim_array:
    ...:     print(id(array))
    ...:     
4674307536
4674308736
4674307536

In [12]: for array in twodim_array:
    ...:     print(id(array))
    ...:     
4673935360
4674307536
4673935360

In [13]: for array in twodim_array:
    ...:     print(id(array))
    ...:     
4674557392
4673935360
4674557392

In [14]: 

In [14]: id(twodim_array[0])
Out[14]: 4673766544

In [15]: id(twodim_array[1])
Out[15]: 4673936160

In [16]: id(twodim_array[2])
Out[16]: 4585491872

In [17]: 


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

In [17]: for int_val in onedim_array:
    ...:     print(id(int_val))
    ...:     
4671588584
4671588608
4671588584

In [18]: for int_val in onedim_array:
    ...:     print(id(int_val))
    ...:     
4671588632
4671588584
4671588632

In [19]: for int_val in onedim_array:
    ...:     print(id(int_val))
    ...:     
4671588656
4671588632
4671588656

In [20]: id(onedim_array[0])
Out[20]: 4671588680

In [21]: id(onedim_array[1])
Out[21]: 4671588704

In [22]: id(onedim_array[2])
Out[22]: 4671588728

In [23]: 

 まとめ

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+4

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

a = np.arange(10)
id(a[0])
id(a[0})
id(a[0})
b = np.arange(10)
id(b[0})
id(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の要素の中身の指す場所をかえます。

import numpy as np

a = np.ones((4,4))
print(a)
for v in a:
    v = np.zeros(4)
print(a)
for v in a:
    v[:] = np.zeros(4)
print(a)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/04 16:09

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

    キャンセル

  • 2018/01/05 00: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
    リンク先に振る舞いについての話があります。

    キャンセル

  • 2018/01/04 17: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()で取得する値は、メモリアドレスそのもののように読めますが、違うのでしょうか?

    キャンセル

  • 2018/01/04 18: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はメモリアドレスとして考えても問題ないということになります。

    キャンセル

  • 2018/01/04 23:09

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

    キャンセル

  • 2018/01/04 23: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]

    キャンセル

  • 2018/01/04 23:46

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

    キャンセル

  • 2018/01/05 00:15 編集

    横から失礼します。
    a[:]は各要素のidが保持されるので、リファレンスではシャロ―コピーと表現されています。
    もちろんmkgreiさんの書いたコードは間違いないですので、単に単語の用法に関する問題ですが。

    ---
    a = [[i] for i in range(3)]
    b = a[:]
    b[0].append(1)

    print(a) # ディープコピーなら[[0], [1], [2]]と出力されてほしいところ

    キャンセル

  • 2018/01/05 00:28

    LouiS0616さん、コメントをありがとうございます。
    やってしまいました。

    中身がmutableの時にうまくコピーされるのがdeep copyでした。
    yosiderさん、申し訳ありません。

    過去のディープコピーのくだりを修正しておきます。

    キャンセル

  • 2018/01/05 17:07

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

    キャンセル

  • 2018/01/05 17:13

    リファレンスの該当部分は以下のものを想定していますか?

    >浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します。
    >深いコピー (deep copy) は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。

    キャンセル

  • 2018/01/05 17:32

    いえ,LouiS0616さんの「a[:]は各要素のidが保持されるので、リファレンスではシャロ―コピーと表現されています。」というご発言を受けてのものであり,私が実際にリファレンスを確認したわけではありません.

    いままで私はオブジェクトxに対して
    y = x
    として得られるyのことをshallow copyというのだと勘違いしておりましたが,お示しのリファレンスによれば,a[:]はまさにshallow copyに当たりますね.

    勉強になります.ありがとうございます.

    キャンセル

  • 2018/01/05 18:10

    段階的にコピーのように振る舞うものが複数あるので紛らわしいですね。

    y = xだとyとxは同じものを指しているのでコピーではないのです。

    そして配列等に対してb = a[:]とするとbはaとは別物として作れるのでコピーになります。でもその中身はできるだけaと同じものになるようにコピーの時間とメモリ消費を抑えます。
    ただ、それだと中身が更に他のものを参照した時に、その先のものの変更が副作用として影響を与えてきます。

    そこで、中身をすべて辿ってコピーを作るディープコピーがたまに必要になるのです。

    リストになんでも入れられるPythonの宿命ですね。

    キャンセル

  • 2018/01/20 17:16

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

    キャンセル

+2

本件は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配列でも似たような結果となりました。 

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


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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/04 12:10

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

    キャンセル

  • 2018/01/04 12:36

    listとnumpy.arrayで実装が異なることからくる混乱の気がします。
    listのほうが実装は自然な気がします。
    中身のidが、それがどのように取り出されたのかにかかわらず一定になりますので。
    Pythonのインタプリタからみて、中身も含めてidを持っているようです。

    numpy.arrayは高速化、他言語実装のせいか、不思議な挙動をしますね。
    Pythonのオブジェクトとして取り出された時に初めてidを与えられるようなのです。
    そしてインタプリタからの参照カウントが0になると開放されているようです。

    変数スコープを正しく与えることで両者の使うメモリはあまり変わりませんが、
    変なことをすることによって、numpy.arrayで「メモリリーク」のようなことを発生させることができそうです。

    キャンセル

  • 2018/01/04 12:43

    > listとnumpy.arrayで実装が異なることからくる混乱の気がします。
    はい、mkgreiさん回答を拝見して「そっちだったか」と気づきました。

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

    キャンセル

  • 2018/01/04 16:15

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

    キャンセル

  • 2018/01/04 16:36

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

    キャンセル

  • 2018/01/04 17:35

    view自体がオブジェクトらしいですね。
    a = np.arange(3)
    print(id(a[0]), id(a[1]))
    で同じidが出るのも驚きです。

    viewについて
    https://deepage.net/features/numpy-copyview.html

    キャンセル

  • 2018/01/04 17:54

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

    キャンセル

  • 2018/01/04 18:09

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

    キャンセル

  • 2018/01/04 18: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に落ち着くような振る舞いをするのです。
    もしかしたらファイルシステムのインデックスのようなものなのかもしれません。

    キャンセル

  • 2018/01/04 19:19

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

    キャンセル

  • 2018/01/04 21: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)))

    キャンセル

  • 2018/01/04 23: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")は多分偽になるでしょうから!

    キャンセル

  • 2018/01/04 23: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になることがあると同じ理由で「違う実体になることがある」ということなのだろうかと思えてきました。どうもそっちの方がありそうな気が・・・

    キャンセル

  • 2018/01/05 00:05

    >>> 1 is 1 #True
    >>> -5 is -5 #True
    >>> -2123928192 is -2123928192 #False
    となるのは、cpython は -5 から 256 までの integer object は同一のものを再利用するかららしいです。
    参考
    https://stackoverflow.com/questions/3402679/identifying-objects-why-does-the-returned-value-from-id-change
    https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong

    キャンセル

  • 2018/01/05 00:12

    たしかにそうですね。
    余計なことを気にしすぎていたのかもしれません。
    失礼しました。

    Pythonが参照渡しと言われている割に振る舞いがC言語でのポインタらしくなかったので妙に気になってしまいました。
    a=-6
    b=a
    b=-7
    print(a)
    とした時にC言語でbをポインタにするとaは変更されるのに対して、ポインタでない代入であれば変更されないので、無駄に混乱していました。

    Pythonでidを使うときは同一性を確かめるときが主だと思っているのですが、数字みたいなものの場合、同一性とは何かについてよく考えないといけない、ということを言いたかったような気がします。

    わざわざお付き合い頂きありがとうございました。

    キャンセル

  • 2018/01/05 00: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文中に例外を発生させると、例外が起きる前まで書き出されてぶつ切りになってしまう現象に通ずるものかと思われます。

    キャンセル

  • 2018/01/05 08: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)になるはずだから当たり前のことを勘違いしていた気がします・・・

    キャンセル

  • 2018/01/05 14: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にならないのか、という誤解でした。

    キャンセル

  • 2018/01/05 14:51 編集

    最初の例の方ですが、numpy.int32などは特にインスタンスの共有を気にしていないということのように思えました。karamarimoさんがコメントくださったようにpython自体はintの実体を作るときによく出現するインスタンスをぼこぼこ生成しないよう配慮していますが、numpyの場合はそれが必要ないという判断なのだと思いました。

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

    ---
    なんか質問者さんより自分の方が勉強させてもらった気分です・・・

    キャンセル

  • 2018/01/05 16:35

    pythonはintについて-5~256までは多重に生成しないようにしています。
    ただそれも、インタプリタのコンパイル環境次第のようで、それが上記で257などでも私の環境は多重生成しないようになっていました。
    (インタラクティブモードだと再現されるので、スクリプトファイルとして実行する際の最適化が入っているせいかもしれません。)

    numpyの場合、必ず新しいものが作られます。(少なくとも試している限りではですが。)
    それを利用することによって、リストにどんどん入れてしまうと参照カウントが0にならず、メモリを圧迫するという意味で「メモリリーク」と表現しました。
    実体に到達できるという意味で「リーク」はしていないのですが。

    PythonのGCについて簡易な読み物として、
    http://postd.cc/visualizing-garbage-collection-in-ruby-and-python/

    Pythonでは自分でメモリマッピングについて考えてしまうと言語実装的に確定的ではない振る舞いをするので、あまり本質的ではないわかりにくい話をしてしまっていました。
    リファレンスにはメモリアドレスと書いてありますし、文字通りに実装もされていますが、ユニークな数字である程度に認識するのが一番良いのかなと思われた次第です。

    キャンセル

0

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

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

numpy云々関係なく、

arr = [0,1,2]

for n in arr:
    n = 5

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

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

twodim_arr = [[i for i in range(j*3,j*3+3)] for j in range(3)]

for arr in twodim_arr:
    arr[1] = 5

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

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

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

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

○蛇足的な何か

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

arr = [5,5,5]

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

arr = [5 for _ in arr]

と書くと良いでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/04 15:57

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

    キャンセル

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

  • ただいまの回答率 91.06%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    Python:Numpy配列の変形

    PythonでNumpy配列を変形する方法についてお聞きしたいです. Numpyで配列にmax関数を適用すると,指定した軸ごとの最大値が得られます. 例) これは,行ごと

  • 解決済

    listからnumpy.ndarrayへの変換方法

    [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0],

  • 受付中

    関数の実装、matmulを利用

    追記:print(np.matmul(A, x)) まではできたのですが、それ以降はどのように書けば良いでしょうか?

  • 解決済

    pythonでmapとfloat

    このようなコードがあり、実行すると このようなエラーがでます import numpy as np cov = np.zeros((region, 2, 2)) sdata =

  • 解決済

    pythonのlist文について

    pythonのプログラムです。 listA[8,4,2] listB[5,2,3] この掛け算の合計を求めたいのですが print sum(listA*listB) でやるとエラー

  • 解決済

    pythonのスライスについて

    a = b[:,0] このようなコードがあったとき、どのようなことがおこなわれますか? bはこのコードがなりたつ何かだとすると なにだったら成り立ちますか? すみません、間

  • 解決済

    python3 リストの一括削除等

    前提・実現したいこと pythonでリストの勉強をしております。 多数の数値を格納しているリストを、例えば100以下の数値は一括して削除、もしくは他の数値や文字列に一括して変更する

  • 解決済

    辞書型のデータをフィルターをかけて必要なものだけ取り出したいです.[python]

    前提・実現したいこと 現在,主成分分析を変数の組み合わせごとにブーストできるツールを作りたい. 今の状態↓ 1.変数のパターン表を作成 2.パターン表をもとに繰り返し処理で因子

同じタグがついた質問を見る

  • Python 3.x

    3705questions

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

  • NumPy

    292questions

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