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のモジュールインポート

書籍『Ruby でつくる Ruby』が発売されます

Rubyで学ぶRuby

ちょいと都会へ行った時本屋を覗いたら、Python本が機械学習とデータ分析をテーマにして、 雨後の筍のように出てた。まるで一頃のレールブームだな。

何か記念に買おうと思ったけど、テーマを絞ったものはオイラーの飽きやすい性格からして敬遠。月並みに、日経ソフトウェア(2017-05)を買った。今見てる。オイラーの知らないのが紹介されてたぞ。

NetworkXメモ(有向グラフ)

NetworkXによるスモールワールドネットワークの生成

これって、一部 Graphviz とかぶっていないかい。