python 環境
この所pythonばかりなので、家に有る関係本を眺めてみた。2冊あった。1冊目は、オライリー のデスクトップリファレンス。2004年に購入してた。
扱っているのは、python 1.5.1というもの。パラパラ見たけど、今でも通用しそうな事が 説明されてる。軸がぶれないで発展してきたんだなあ。 emacsのpythonモードなんてのも載ってて、健在ぶりを示しているぞ。
もう一冊は、空飛ぶPython即時開発指南書って本。
2013年に頂いたもの。何故かmatzさんのサイン入り。栞に馬券が挟まっていた。アイアムルビーって言う馬のやつ。こちらもご丁寧にmatzさんのサイン入り。寄贈してくれたうーさん、どうも ありがとう。今頃役に立っていますよ。
>>> import pg ac8+j463Pn >>> import pg >>> import imp >>> imp.reload(pg) ac8+j463Pn <module 'pg' from 'pg.pyc'> >>> imp.reload(pg) ac8+j463Pn <module 'pg' from 'pg.pyc'>
モジュールは、ロード時に1回だけ評価される。だから、モジュールを開発してる時、それを 書き換えてimportしても、再評価されない。
こういう時は、impを入れておいて、reloadすれば、都度評価される。知らなかったぞ。
idle
上の本をパラパラしてたら、idleなんて言う、GUIの開発環境の紹介が出てた。いやな人は、 CUIでreplを使え。どちらもOK。
GUI版は、Tcl/Tkを元にしたもの。久しぶりに触ってみるか。OpenBSDで3.6を自前コンパイル した時も自然にコンパイルされてたしね。ああ、自然に入ったと言うと、pipもインストール時に 自動で導入されてたね。
GUI版は永らく使う気は無いんだけど、気が利いたのが3点程有ったので、心に留めておく。
一点目は、ファイルメニューにある、Path Browser。起動すると、有効になっているパイソンのパスが出て来る。フォルダーマークをクリックして行くと、ファイルが現れ。それをクリック するとファイルが別なウィンドウで開かれる。ファイルはモジュールなんで、自然にモジュールと接する事になる。
pathの中に、~/.local/lib/python3.6/site-packages が含まれていた。自前のパッケージは ここに入れておくといいんだな。
二点目もやはり、ファイルメニューにある、Class Browser。起動するとクラス名を聞いて くるんで、randomとか指定すると、そのファイルが別ウィンドウで開く。そして更に別ウィンドウが開き。クラス階層図が出て来る。これをクリックすると、先ほど開いたファイルの該当箇所が強調表示される。なかなか良いインターフェースだと思うぞ。
三点目は、ヘルプメニューに Turtle Demo。起動すると、やはり別なウィンドウが開く。 Examplesをクリックすると、実行したいデモが選べる。clockなんてのを選ぶと、左ペインに ソースが、右ペインがキャンバスになってて、舌のstartボタンを押すと、ちょっとスイス鉄道 風のアナログ時計が時を刻んでいた。この時計、なかなか良いデザインだな。
他にも、色々なサンプルが有るので楽しみたまえ。 ソースを眺めるだけで、どんな絵が出てくるか分かる人は、/lib/python3.6/turtledemo/*.py を眺めればおk。(そんな人居るんか?)
Python startup
前回、ひょんな事からpythonにもPrettyPrinterが有る事を知った。replで面倒かけずに 使いたいな。それには、python起動時に、自動読み込みしたいんだけど、.bashrcみたいなのの 決まったファイルってのは有るの?
調べたら、そんなの任意の名前のファイルでいいよ。好きな場所に置いておいていいよって 自由度200%増し(対bash比)な仕組みだそうだ。そして、そのファイルは、PYTHONSTARTUPの 環境変数に登録しとけとな。
自由な名前でいいと言われても、それっぽい名前を付けるのがpython流。
[ob: ~]$ cat .pythonrc.py import pprint pp = pprint.pprint
そして、参照命令。
export PYTHONSTARTUP=~/.pythonrc.py
更に、環境調査部
[ob: Python-3.6.0]$ find . -name '*.[ch]' | xargs grep PYTHONSTARTUP ./Modules/main.c:PYTHONSTARTUP: file executed on interactive startup (no default)\n\ ./Modules/main.c: char *startup = Py_GETENV("PYTHONSTARTUP"); ./Modules/main.c: PySys_WriteStderr("Could not open PYTHONSTARTUP\n");
emacsでpython語入力時の補完
python3になって、repl上で補完が利く。emacsではpython.elを静かに使っているんだけど、 こちらは補完が利かないと言うアンバランスです。
コードを書いている時程、補完が欲しいのに、なんたる有様。なんとかしよう。elpaから、 emacs-jediとauto-completeを入れる。 init.elの設定は下記ね。(色も色々有りまして)
(require 'auto-complete-config) ;(ac-config-default) (setq ac-use-menu-map t) (set-face-background 'ac-completion-face "green") (set-face-foreground 'ac-candidate-face "red") (set-face-background 'ac-selection-face "blue") (set-face-foreground 'popup-summary-face "red") (set-face-background 'popup-tip-face "cyan") (set-face-foreground 'popup-tip-face "blue") (add-hook 'python-mode-hook 'jedi:setup) (setq jedi:complete-on-dot t)
やったら、emacsがエラーを吐き出した。陸の孤島のOpenBSDならではの珍事? かな。 怒りの真相は、epcサーバーが起動出来ないって事らしい。そうか、補完はpythonに頼る んで、裏でpythonを動かし、そこと通信したいんだな。
elpa/jedi-core-20170121.610/jediepcserver.py の最後の行でエラーだとよ。 emacs陣営の中にpythonスクリプトが紛れ込んでいる。諦めて開腹。
みたら、mainの所、その近辺に、import jediってのが有った。そうか、そういう奴が必要なのね。pip download jedi; pip install jedi と言う手間をかけてインストール。(直接 インストールするとオイラーの環境ではタイムアウトする為)
これで走るかと思ったらやはりエラー。該当の行を見たら、
def get_jedi_version(): import epc import sexpdata return [dict( name=module.__name__, file=getattr(module, '__file__', []), version=get_module_version(module) or [], ) for module in [sys, jedi, epc, sexpdata]]
ここのepcが無いとの事。これもpipで入れたら、やっと動き出した。やれやれ。sexpdataと言う、興味を引くモジュールも同時に付いてきた。これはきっと、むふふをDLしてくれるに 違いないと期待して開いてみたら、emacsとpython間のデータフォーマット変換器だった。 S式とjsonとかね。隠れcarとかcdrも居て、これは別の意味で面白いぞ。作者さんは、お名前 からして日本人とお見受けした。おもてなしがしっかり隠れていたぞ。
importって、ファイルの冒頭付近に書いておくのが、一般の習慣。勧められる態度なんだけど、 こういう風にする理由な何だろう。バージョンの整合性をまとめてちぇっくかな。
関数の中でimportしたやつは、外へ染み出す仕様なのかしら? どうも、importには 深い闇が有るな。
上の色使いは、M-x list-faces-display とすると見本が出てくるぞ。
freeze
上の本によると、普通のPythonでも、スクリプトをコンパイルして、バイナリーファイルを 作成出来るそうだ。条件として、Pythonのソース一式とコンパイル環境が必要。
[ob: py]$ python ~/MINE/Python-3.6.0/Tools/freeze/freeze.py pg.py Name File ---- ---- m __future__ /home/sakae/MINE/lib/python3.6/__future__.py m __main__ pg.py m _ast : freezing __future__ ... freezing __main__ ... freezing _bootlocale ... : Warning: unknown modules remain: _bisect _blake2 _bz2 _datetime _hashlib _heapq _lzma _md5 _opcode _pickle _posixsubprocess _random _sha1 _sha256 _sha3 _sha512 _socket _ssl _struct binascii grp math pyexpat readline select termios zlib generating table of frozen modules Now run "make" to build the target: pg
Makefile,config.c frozen.cと大量の頭にMが付くファイルが作成された。そのMファイルの 例。これってPythonVMのバイトコードかな。
[ob: py]$ lv M_encodings.c unsigned char M_encodings[] = { 227,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0, 0,64,0,0,0,115,128,0,0,0,100,0,90,0,100,1, : 8,28,8,85,10,2,10,1,8,10, };
一方、frozen.cの方は、ext宣言に続いて、下記のようなのがずらーと並んでいた。多分 メモリーに展開するのだろう。
: static struct _frozen _PyImport_FrozenModules[] = { {"__future__", M___future__, 4167}, {"__main__", M___main__, 399}, : {0, 0, 0} /* sentinel */ }; int main(int argc, char **argv) { extern int Py_FrozenMain(int, char **); PyImport_FrozenModules = _PyImport_FrozenModules; return Py_FrozenMain(argc, argv); }
取り合えずmake
[ob: py]$ make : M_zipfile.o /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a -lpthread -lutil -lm -o pg /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a(getpath.o): In function `ismodule': ../Modules/getpath.c:163: warning: warning: wcscat() is almost always misused, please use wcslcat()
最後に、OpenBSDな人に怒られつつバイナリが出来た。
[ob: py]$ file pg pg: ELF 64-bit LSB shared object, x86-64, version 1 [ob: py]$ ./pg So7;hG_i:W [ob: py]$ python pg.py So7;hG_i:W
当たり前だけど、バイナリーファイルとスクリプトで実行結果は同一。よかったね、と安心 してはいけない。実行バイナリーファイルの巨大さにびっくりしてください。その原因は、 python関係者を全てファイル内に抱えてこんでいる為。リンクされてるのは、極普通な奴。 これなら、同一OS間で受け渡し可能だな。
[ob: py]$ ls -lh pg -rwxr-xr-x 1 sakae sakae 10.4M Mar 24 16:25 pg* [ob: py]$ ldd ./pg ./pg: Start End Type Open Ref GrpRef Name 0000021bb7c00000 0000021bb8725000 exe 2 0 0 ./pg 0000021dfbe9b000 0000021dfc2a9000 rlib 0 1 0 /usr/lib/libpthread.so.22.0 0000021e2c63b000 0000021e2ca47000 rlib 0 1 0 /usr/lib/libutil.so.12.1 0000021e3b0d9000 0000021e3b501000 rlib 0 1 0 /usr/lib/libm.so.9.0 0000021de016c000 0000021de0636000 rlib 0 1 0 /usr/lib/libc.so.88.0 0000021e80000000 0000021e80000000 rtld 0 1 0 /usr/libexec/ld.so
refs
PythonのGCを停止させて性能向上を図ったですって。マークアンドスイープの方を 切って、CoWが起こらないようにしたとか。初めて知った。
Dismissing Python Garbage Collection at Instagram
PythonのGCは、レファレンスカウント方式ってのは、良く知られた事。あるオブジェクトが 使われる(他から指される)度に、カウントが増加してく。使わなくなったらカウント値を 減らしていきZEROになったら解放をする約束。
それを守らないとメモリーリークが発生する。で、debugオプションを付けてコンパイルしたやつは、オプションを付けて起動すると、総カウント数と、メモリーの使用ブロックを報告して くるようになる。
[ob: py]$ python -X showrefcount >>> [78235 refs, 24275 blocks] >>> import os [78237 refs, 24275 blocks] >>> import pg So7;hG_i:W [83696 refs, 26099 blocks] >>> del pg [83694 refs, 26103 blocks] >>> quit() [83696 refs, 26103 blocks] [24635 refs, 8329 blocks]
起動時には、色々なオブジェクトが作られているんで、大きな数になっている。モジュールを 読み込む等をすると、どんどんとカウント値も使用ブロック数も増えていく。
delで解放しても、refsの減少は極わずか、使用ブロックは増えているぞ。こんな事で 宜しいのでしょうか? 最後は行儀よく、使ってたのをなるべくクリーンにして、終了 してる。OS側の後始末軽減に協力してんだな。
で、ちょっと気になったの、blockって何? 普通は4kで1ブロックかと思うんだけど。 思い悩んでいてもしょうがない。実験して観察だな。
>>> import math [78664 refs, 24375 blocks] >>> a = [math.sqrt(x) for x in range(10_000)] [88675 refs, 34408 blocks] >>> 88675 - 78664 10011 [88684 refs, 34411 blocks] >>> 34408 - 24375 10033 [88684 refs, 34411 blocks] >>> del a [78682 refs, 24510 blocks]
10k個の浮動小数点を持つ、配列を作った。ブロック数も10k個だなあ。と言う事は、 ブロック数ってのは、オブジェクト数そのものなんだな。refsの方は、リンクって事に なるんかな。正確にはソース嫁。
@ Objects/object.c void _PyDebug_PrintTotalRefs(void) { PyObject *xoptions, *value; _Py_IDENTIFIER(showrefcount); xoptions = PySys_GetXOptions(); if (xoptions == NULL) return; value = _PyDict_GetItemId(xoptions, &PyId_showrefcount); if (value == Py_True) fprintf(stderr, "[%" PY_FORMAT_SIZE_T "d refs, " "%" PY_FORMAT_SIZE_T "d blocks]\n", _Py_GetRefTotal(), _Py_GetAllocatedBlocks());
これだな。ここから辿って行けばいいんだな。道を辿ったら、obmalloc.cに案内された。 ざっと見、伝統的なGCで管理されてるっぽい。
dlopen
ちょっと趣向を変えて、pg.pyを呼んだ時、どんなライブラリーがロードされるか調べてみる。 ダイナミックロードは、dlopenだったな。
Breakpoint 1, dlopen ( libname=0x1641f11c9648 "/home/sakae/MINE/lib/python3.6/lib-dynload/_heapq.so", flags=2) at /usr/src/libexec/ld.so/dlfcn.c:51 51 { Breakpoint 1, dlopen ( libname=0x1641e2b00d30 "/home/sakae/MINE/lib/python3.6/lib-dynload/math.so", flags=2) at /usr/src/libexec/ld.so/dlfcn.c:51 51 { : Breakpoint 1, dlopen ( libname=0x16421b6f8208 "/home/sakae/MINE/lib/python3.6/lib-dynload/_random.so", flags=2) at /usr/src/libexec/ld.so/dlfcn.c:51 51 {
randomモジュールを下支えしてる、色々なのがロードされてるな。
そして、上で見てたobmalloc.c周辺にあった、debugスタブを有効化。
Breakpoint 2, new_arena () at ../Objects/obmalloc.c:1031 1031 if (debug_stats == -1) { (gdb) set debug_stats = 1 (gdb) c Continuing. Small block threshold = 512, in 64 size classes. class size num pools blocks in use avail blocks ----- ---- --------- ------------- ------------ # times object malloc called = 4 # arenas allocated total = 0 # arenas reclaimed = 0 # arenas highwater mark = 0 # arenas allocated current = 0 0 arenas * 262144 bytes/arena = 0 # bytes in allocated blocks = 0 # bytes in available blocks = 0 0 unused pools * 4096 bytes = 0 # bytes lost to pool headers = 0 # bytes lost to quantization = 0 # bytes lost to arena alignment = 0 Total = 0 (gdb) c Continuing. Small block threshold = 512, in 64 size classes. class size num pools blocks in use avail blocks ----- ---- --------- ------------- ------------ 4 40 1 2 99 5 48 1 1 83 7 64 1 2 61 9 80 1 38 12 11 96 1 1 41 12 104 5 157 33 13 112 6 184 32 14 120 15 471 24 15 128 20 594 26 17 144 1 1 27 20 168 1 1 23 22 184 1 1 21 23 192 1 1 20 24 200 1 14 6 26 216 1 2 16 29 240 1 1 15 30 248 1 1 15 34 280 1 1 13 39 320 1 2 10 40 328 1 6 6 58 472 1 1 7 60 488 1 1 7
案内を見ると、8バイトの倍数単位でバッファーが用意されてて、要求のあったサイズに近い 部分を使うようにしてるとな。細かいな。
python -v
1. コマンドラインと環境を見ると、-d オプションとか、-vvオプションとか、楽しいのが有る。今回は、-vと言うのを試してみる。
[ob: py]$ python -v pg.py |& grep executed # extension module '_heapq' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_heapq.so' # extension module 'math' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/math.so' # extension module '_hashlib' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_hashlib.so' # extension module '_blake2' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_blake2.so' # extension module '_sha3' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_sha3.so' # extension module '_bisect' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_bisect.so' # extension module '_random' executed from '/home/sakae/MINE/lib/python3.6/lib-dynload/_random.so'
上で、gdbから触ったのを、簡単に確認出来た。 更に、詳細なのを見ると
import _frozen_importlib # frozen import _imp # builtin import sys # builtin import '_warnings' # <class '_frozen_importlib.BuiltinImporter'> import '_thread' # <class '_frozen_importlib.BuiltinImporter'> : # /home/sakae/MINE/lib/python3.6/__pycache__/posixpath.cpython-36.pyc matches /h ome/sakae/MINE/lib/python3.6/posixpath.py # code object from '/home/sakae/MINE/lib/python3.6/__pycache__/posixpath.cpython -36.pyc' : So7;hG_i:W # clear builtins._ # clear sys.path # clear sys.argv : # cleanup[2] removing builtins # cleanup[2] removing sys # cleanup[2] removing _frozen_importlib : # cleanup[2] removing hashlib # destroy hashlib # cleanup[2] removing _hashlib # destroy _hashlib # cleanup[3] wiping _frozen_importlib # destroy _frozen_importlib_external # cleanup[3] wiping _imp
等々、呼んできて仕事させて、終わったら亡きものにすると言う、非情さを確認出来るぞ。-vvにすると、ちょっと詳細になって、呼んでくる時の候補も表示してくれる。
etc
ちょいと都会へ行った時本屋を覗いたら、Python本が機械学習とデータ分析をテーマにして、 雨後の筍のように出てた。まるで一頃のレールブームだな。
何か記念に買おうと思ったけど、テーマを絞ったものはオイラーの飽きやすい性格からして敬遠。月並みに、日経ソフトウェア(2017-05)を買った。今見てる。オイラーの知らないのが紹介されてたぞ。
これって、一部 Graphviz とかぶっていないかい。