Pythonのsiteモジュールはサイト固有のパスをモジュール検索パスに追加するわけだが

addpackage関数を読んでいたら

for line in f:
    if line.startswith("#"):
        continue
    if line.startswith(("import ", "import\t")):
        exec line
        continue
    line = line.rstrip()
    dir, dircase = makepath(sitedir, line)
    if not dircase in known_paths and os.path.exists(dir):
        sys.path.append(dir)
        known_paths.add(dircase)

最初のifはコメント対応で次はpthでimportではじまる場合にはimportするってことだ。最後は行のパスがまだモジュール検索パスに含まれていなかったら追加するようになっている。

またmain関数は設定が出力されるようになっている。

$ python /opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site.py
sys.path = [
    '/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6',
    ...
    '/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/PyObjC',
    '/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/wx-2.8-mac-unicode',
]
USER_BASE: '/Users/kzfm/.local' (exists)
USER_SITE: '/Users/kzfm/.local/lib/python2.6/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

あれ、USER_SITEってことはローカルにライブラリインストールして使えるってことか?

easy_installのヘルプを見たら--userオプション入れればローカルにインストールすることが出来るって書いてあった。いつもvirtualenv使っているから知らなかった。

ProductName エキスパートPythonプログラミング
Tarek Ziade
アスキー・メディアワークス / 3780円 ( 2010-05-28 )


pycとは一体何なのか?

pycはバイトコードのキャッシュファイルなんだが、実際に中身がどうなっているかはpy_compile.pyのソースコードを読んで理解した。

with open(cfile, 'wb') as fc:
    fc.write('\0\0\0\0')
    wr_long(fc, timestamp)
    marshal.dump(codeobject, fc)
    fc.flush()
    fc.seek(0, 0)
    fc.write(MAGIC)

ここで、codeobjectは

codeobject = __builtin__.compile(codestring, dfile or file,'exec')

でコンパイルしたコードでMAGICはimp.get_magic()で得られたマジックナンバー、wr_longはリトルエンディアンで32bitの整数値を書きこむ関数(py_compileで定義されてる)

つまり、先頭の4バイトがマジックナンバーで次の4バイトにタイムスタンプが続き、その後にmarshalでダンプされたコードオブジェクトが続いたものがpycファイルということだ

実際にpy_compile.pycで確かめてみる。まずマジックナンバーは

>>> import imp
>>> imp.get_magic()
'\x03\xf3\r\n'

タイムスタンプ

>>> l = long(os.stat('py_compile.py').st_mtime)
>>> hex(l & 0xff)
'0xbbL'
>>> hex((l >>8) & 0xff)
'0x8L'
>>> hex((l >>16) & 0xff)
'0x8fL'
>>> hex((l >>24) & 0xff)
'0x4dL'

pycをbviで開いて確かめてみる

00000000  03 F3 0D 0A BB 08 8F 4D 63 00 00 00 00 00 00 00 00

確かに先頭からマジックナンバー、タイムスタンプの順に入っていることがわかる。

ところで、なぜ最初に'\0\0\0\0'を入れてからfseekで先頭に戻ってきて改めてマジックナンバーを書き出しているんだろうか?

Pythonでまだ読んでない標準モジュールのリスト

wikiで必要なデータってこれぐらいしかなかった。

まだ読んでないPython標準モジュール

  • BaseHTTPServer.py
  • Bastion.py
  • CGIHTTPServer.py
  • Cookie.py
  • DocXMLRPCServer.py
  • SimpleHTTPServer.py
  • SimpleXMLRPCServer.py
  • SocketServer.py
  • _strptime.py
  • _threading_local.py
  • aifc.py
  • asynchat.py
  • asyncore.py
  • atexit.py
  • audiodev.py
  • bdb.py
  • cProfile.py
  • calendar.py
  • cgi.py
  • cgitb.py
  • chunk.py
  • cmd.py
  • code.py
  • codecs.py
  • codeop.py
  • cookielib.py
  • copy.py
  • copy_reg.py
  • csv.py
  • decimal.py
  • difflib.py
  • dircache.py
  • doctest.py
  • dumbdbm.py
  • dummy_thread.py
  • filecmp.py
  • fileinput.py
  • formatter.py
  • fpformat.py
  • gettext.py
  • gopherlib.py
  • gzip.py
  • hashlib.py
  • heapq.py
  • hmac.py
  • htmlentitydefs.py
  • htmllib.py
  • httplib.py
  • ihooks.py
  • imaplib.py
  • imputil.py
  • locale.py
  • macpath.py
  • mailbox.py
  • mailcap.py
  • mhlib.py
  • mimetools.py
  • mimetypes.py
  • mimify.py
  • modulefinder.py
  • multifile.py
  • nntplib.py
  • optparse.py
  • os.py
  • os2emxpath.py
  • pdb.py
  • pickle.py
  • pickletools.py
  • pipes.py
  • pkgutil.py
  • platform.py
  • popen2.py
  • poplib.py
  • posixfile.py
  • posixpath.py
  • pprint.py
  • profile.py
  • pstats.py
  • pty.py
  • py_compile.py
  • pyclbr.py
  • pydoc.py
  • quopri.py
  • random.py
  • re.py
  • repr.py
  • rexec.py
  • rlcompleter.py
  • runpy.py
  • sched.py
  • sha.py
  • shelve.py
  • shutil.py
  • site.py
  • smtpd.py
  • smtplib.py
  • sndhdr.py
  • socket.py
  • string.py
  • stringold.py
  • stringprep.py
  • subprocess.py
  • symtable.py
  • tabnanny.py
  • tarfile.py
  • textwrap.py
  • threading.py
  • timeit.py
  • toaiff.py
  • token.py
  • tokenize.py
  • trace.py
  • traceback.py
  • unittest.py
  • urllib.py
  • urllib2.py
  • uuid.py
  • warnings.py
  • wave.py
  • xdrlib.py
  • xmllib.py
  • xmlrpclib.py

Python の可変引数は関数定義だけじゃなくて実行の際にも使える

defするときに*とか**といった可変引数を取れるのはおなじみ

>>> add = lambda x,y: x+y
>>> l = [1,2]
>>> add(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 2 arguments (1 given)

このキーワードは関数実行の際にも使えることを今日知った

>>> add(*l)
3

知ってしまえば自然に感じるが、なぜそういう思考に至らなかったのだろうか?

さくらのVPSにWiki入れるかどうか迷った結果Sphinxでいいことになった

計画停電で自宅サーバーがしょっちゅう止まったり、電気代比較してみると、さくらのVPS使ったほうがエコっぽいので移行することにした。

最近楽しく移行作業を始めたわけだが、ちょっと悩んでいたのがwikiをどうするかだ。そもそも自分がなんでwikiを必要としているのかをよくよく考えてみるに(blogに書くまでもないことの)メモまたは備忘録を構造化された文書形式で残しておきたいということかなぁと。

今はHikiを使っているんだが、利用している理由がEmacsのHikiモードがあるというのが一番大きいし、一人でしか使ってないしなぁ。

となると

  • 構造化された文書形式
  • Emacsのようなエディタで気軽に編集できる
  • それが、さくっとhtmlにコンバートできる
  • あとで見返せる自分用メモ

を満たせばwikiでなくても構わない。で、Sphinxが候補にあがった。以前SAR Newsの原稿書いた時に使ってたので、使い勝手が非常にいいことは分かっているが、運用どうすっかなと悩んでいたら渋川さんにアドバイスもらった

さくらのVPSではwikiのかわりにSphinxでいくことにした

ProductName エキスパートPythonプログラミング
Tarek Ziade
アスキー・メディアワークス / 3780円 ( 2010-05-28 )


Pythonでロードされているモジュールはsys.modulesをみればいい

sys.modulesにリスト化されてる

>>> from sys import modules
>>> modules1 = set(modules.keys())
>>> import sqlite3
>>> modules2 = set(modules.keys())
>>> modules2.difference(modules1)
set(['sqlite3.datetime', 'datetime', 'sqlite3._sqlite3', '_sqlite3', 'time', \
'sqlite3', 'sqlite3.dbapi2', 'sqlite3.time'])

ふむ

import *とimport __all__でモジュールの読み込みを続けてやる理由がわからん

標準モジュールの_dummy_threading.pyを読んでいて

51     from _dummy_threading import *
52     from _dummy_threading import __all__

これってなんなの?なんの意味があんの?ドキュメント読んでみたけどわからんかった。

disのソースコード読んだ

disassembleっていう関数が面白かった

例えば

>>> add = lambda x,y:x+y

っていう関数を定義してdisってみると

>>> import dis
>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_FAST                1 (y)
              6 BINARY_ADD          
              7 RETURN_VALUE

となっている。

バイトコード命令はfunc_code.co_codeで見ることが出来る

>>> add.func_code.co_code
'|\x00\x00|\x01\x00\x17S'

これは整数値を文字にしたものをつなげた文字列として表現されているので、逆の操作をしてopcodeモジュールのopnameで対応を取ることができる

>>> from opcode import opname
>>> code = add.func_code.co_code
>>> for c in code:
...   print opname[ord(c)]
... 
LOAD_FAST
STOP_CODE
STOP_CODE
LOAD_FAST
POP_TOP
STOP_CODE
BINARY_ADD
RETURN_VALUE

Pythonでライブラリの抽象化

anydbmっていうdbm系の汎用インターフェースがあり、どうやって抽象化してんのかな?とソースコードを読んでみた。

_names = ['dbhash', 'gdbm', 'dbm', 'dumbdbm']
_defaultmod = None

for _name in _names:
    try:
        _mod = __import__(_name)
    except ImportError:
        continue
    if not _defaultmod:
        _defaultmod = _mod
    _errors.append(_mod.error)

__import__組み込み関数を使って_namesリストにあるモジュールがインポート出来るか試していって最初にインポートできたモジュールを返す

dropboxを使ってブログのデータをオンラインに退避出来るようにした

このブログのデータはSQLiteに溜まっているので、SQLiteのファイル(サイズ10M程度だし)をバックアップすればとりあえずはOK

サーバーのSQLiteデータファイルを手元のmacbookのDropboxフォルダに持ってくればオンラインにも自動的にバックアップされるじゃないかということでparamikoを利用してsftpで定期的にgetするようにした。

import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='[host]', username='[user]', password='[password]')
sftp = ssh.open_sftp()
remotepath='[remote_datafile]'
localpath='[/Users/user/Dropbox/datafile]'
sftp.get(remotepath, localpath)

ProductName 実用SSH 第2版―セキュアシェル徹底活用ガイド
Daniel J. Barrett
オライリー・ジャパン / 5040円 ( 2006-11-22 )