上記は数学での内包の説明である。本来「理解」という意味なのだが、数学用語としては「内包」というのがある。Pythonの場合、集合だけではなく、リスト、辞書にも使える。次のコードは、1から1000万までの数をリストに順番に入れる。a.append(i)これの実行時間を以下のようにして計測した。t02.0536093711853027約2秒かかっている。次に、同じリストを内包を使って作ってみた。前の測定と同じ条件にするために入れた。結果は、内包を使うことで、2.05秒が0.36秒になり、5倍以上高速になったようだ。内包にすることで、短くなり、速度も上がるようなので、内包が使える場合にはどんどん使った方が良さそうだ。いうことで、もっと内包についてネチネチ調べていこう。TweetPythonでは、数値もオブジェクトである。そのため、相当無駄にメモリが使われてしまうことが分かった。要するに、Pythonでいっぱい計算するのは、メモリ浪費があるから気をつけなければならない。Learningなどでは、大量の数値データを扱うのが普通だ。それも浮動小数点数になるので、全て別々のオブジェクトになる。考えると、PythonはAIには全然向いていない。いっても、素のPythonを使うことはなく、PythonにNumPyをインポートし、さらに色々なものをインポートして使う訳だ。そもそも、Pythonにはリストはあっても配列がない。リストは、その性格上遅い。いうことで、NumPyの配列がどのくらいメモリを食うか調べてみる。前回と同じサイズで、1000万要素の配列を作って調べた。sz1-sz080531456最初に必要そうなものを全部importした。numpy.arrayに、要素数1000万個のリストを渡して配列を作った。渡した巨大なリストは、用済みになった時点で消えるはずである。80531456バイトであり、1要素あたり8バイト強しか増えていない。確認のため、配列オブジェクトについて調べてみる。sys.getsizeof(a)80000096ちゃんとnumpy.ndarrayという型になっており、1000万要素の配列のバイト数は、8000万96バイトになっている。余分の96バイトは、配列オブジェクトの管理用などに使われていると思われる。Pythonのリストでは、1要素あたり40バイトも増えていたのが、実際のデータのサイズと思える8バイトだけの増加になっている。このことから、NumPyの配列では、数値自体はオブジェクトになっていないと言える。C言語などと同じように、配列に直接数字が入っているのだ。Tweet前回の実験で、同じ数字が別オブジェクトになるとしか考えられない実行結果が得られた。今回は、それをきちんと確認しよう。140552436779360数値のIDを調べるのに、一々変数に入れているが、これが重要なのだが、それについては後で説明する。123をaとcに代入している。IDは全て同じになっている。次に、もうちょっと大きな数字として、12345で実験してみた。140552438458512aとbはちゃんと同じIDになっているのだが、cのIDは異なる。bの指すオブジェクト12345と、cのオブジェクト12345は異なっている。小さい数の場合は、同じ整数値なら同じ整数値オブジェクトになるが、大きな値は違うようだ。これをきちんと確認してみるために、次のプログラムを実行してみた。140552398636944どうやら、256までは同じ整数オブジェクトで、257以上の場合、別の整数オブジェクトが生成されてしまうようだ。これが、メモリを食っていた原因のようだ。実数の場合にどうなるかを試してみた。140552438575512実数の場合、1.0でも異なる実数オブジェクトが確保されるようだ。いうことは、実数データが大量にあると、すべて別オブジェクトになってしまうので、メモリを食い潰す可能性があるので要注意だ。最後に、変数に代入してからIDを調べていたが、もし変数に代入しないでIDを求めるとどうなるか試してみた。id(123456)140552438458512数値が異なるのに、今度はIDが全部同じになっている。?説明は省略するので、自分で考えよう。TweetPythonは、整数もオブジェクトになることが分かった。今回は、要素が整数の長大なリストを作って、メモリがどのように消費するかを調べてみる。求まることは既に説明した。今回は、プロセスのメモリがどういう状態になっているかを調べてみよう。ipythonの方が対話性能を頑張っている分、余計にメモリを食ったり、もろもろのことがあるので、よりシンプルなpython3で実験する。os.getpid()15896次に、psutilをインポートし、pidからカレントプロセスのオブジェクトを作る。psutil.Process(os.getpid())これで、プロセスの様々な情報を得る準備ができた。今回はメモリに注目するので、プロセスのmemory_info()を使う。それでもたくさんの情報が出てきてウザイので、rssだけを表示しよう。current_process.memory_info().rss12869632ここまでが準備だ。プロセス関連の情報を得る方法を知っていると、色々便利なので、いざという時に使えるようになっておくことは重要だ。巨大なリストを作ってみよう。0が10000000個(1千万個、10M個)のリストを作ろう。作る前と後で、使用メモリサイズを計測する。sz1-sz080326656要素数10Mのリストを作って、使用メモリが80MB増加しているので、1要素あたり8バイト増加していることになる。リストの中身は全部0なので、同じ0オブジェクトが使われている訳で、整数のオブジェクトは1つ作られただけで、消費メモリに影響しない。次に、全て違う値を入れてみよう。sz2-sz140513945610M個に対して約400MB増加しているので、1要素あたり40バイト増加したことになる。リストの要素自体のサイズは8バイトなので、整数オブジェクトのサイズは32バイトのようだ。sz3-sz2405295104bと同じ内容のリストcを作って、ほぼ同じ400MB増加した。はて、何が起きているんだろう。Tweetどうやら、数もオブジェクトのようだ。そのオブジェクトのバイト数を調べることができるのでは。用意されていたので、1のサイズを求めてみた。28しかし、結果は28バイト。なんだかやらたに大きい。2}どうやら、数字を1つ増やすごとに8バイト増えるようだ。空リストは64バイト、空タプルは48バイト、{}は空集合ではなく、空辞書になるようだ。集合や辞書は、要素数に比例して大きくなるのではなく、たぶん階段状に増えていくものと予想される。Pythonでは何でもオブジェクトになり、オブジェクトは単にデータが入っているだけではなく色々な物、仕掛けを隠し持っていて、そのためにサイズが増えているようだ。仕掛けの中には、ガーベッジコレクションも含まれているはずだ。ここまでは、何でもオブジェクトにしたためと思われるので納得できる。次に、文字列について調べた。+-*/'a'は58バイト、'ab'は51バイト。同じ1文字でも、アルファベットは58バイトで、記号は50バイトのようだ。この不思議はどう考えればよいのだろうか。[[]]*6による繰り返しは、結局同じものを指していたのだが、実際に値を入れて変になることで確認した。今回は、もっと便利な、明白な方法で調べる。Pythonは何でもオブジェクトにしてしまう。それをちょっとだけ実験する。id(オブジェクト)なる関数があり、引数に何でも良いから入れると、そのオブジェクトID(識別子)を返してくる。これを使って、調べてみよう。print("%0x\n%0x\n%0x"%(id(B[0]),id(B[1]),id(B[5])))7f7817d2e5c87f7817d2e5c87f7817d2e5c8まったく同じIDなので、[]のオブジェクトは1つしか無いことが分かった。Pythonのオブジェクト、とくにリストは、リストの中にリストを自由に入れられ、複雑な構造も表現できる。いうオブジェクトをちゃんとコピーすることを考えよう。Pythonでオブジェクトをコピーする方法がいくつかあるので、その比較実験を行ってみる。copy.deepcopy(C)これで、元のオブジェクトAと、色々な方法でコピーしたB,C,D,Eが揃った。==で等しいかどうかチェックしてみた。True)同じである。いっても、これは見かけだけの可能性があるので、IDを調べよう。print("%0x\n%0x\n%x\n%x\n%x\n"%(id(A),id(B),id(C),id(D),id(E)))7f78179aeb087f78179aeb087f78179b46087f78179b0fc87f78179efc08AとBは全く同じであり、C,D,Eはそれぞれ別のオブジェクトである。print("%0x\n%0x\n%x\n%x\n"%(id(A[1]),id(C[1]),id(D[1]),id(E[1])))7f78179efa487f78179efa487f78179efa487f78179efd48deepcopyしたEだけが異なることが分かった。コピーしたいときには、deepcopyしないとダメである。次に、リストの最初の要素のIDを調べた。print("%0x\n%0x\n%x\n%x\n"%(id(A[0]),id(C[0]),id(D[0]),id(E[0])))7f7835ba5a207f7835ba5a207f7835ba5a207f7835ba5a20全部同じである。リストの最初の要素はただの数字、整数の1であった。Tweetとても簡単なリストの実験をしてみよう。リストで繰り返しがあるとき、*で済ませると楽ができる。TrueリストA,Bは当然同じ動きをする。リストの要素を数字ではなく、リストに変えてみよう。[1]]FalseリストAとBを2つの方法で作り、一致していることを確認した。次に、A[3]とB[3]に1をappendした。AとBは同じで、同じ操作をしたのだから、結果も同じになるはずなのだが、なっていない。より[]を6個にしても、実体は1つのままのようだ。このでは、[]で実験したが、オブジェクトだったら全て同様の結果になるであろう。オブジェクトを指している変数を、別の変数に代入しても同じオブジェクトを指すようになるだけというのが良く説明されているが、こういう場合にも同じ現象が当然起きる。実験では、[]が6個だから、全部書いてもたいしたことはない。検索このアーカイブについてこのページには、2016年12月に書かれたブログ記事が新しい順に公開されています。前のアーカイブは2016年11月です。次のアーカイブは2017年1月です。最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。