valueでsortされたdictが欲しい

という話。

カウントしつつリアルタイムで現在のヒストグラムを確認したいなーと思った。

こういうときは普通パフォーマンスが問題になるから自分でデータ構造を書くんだろうけど。。。
とりあえずbuilt-in、pure pythonで実現するもっともしょぼい方法でやってみた。

class RankDict(object):
    def __init__(self):
        self.__idict  = dict()
        self.__clist = []

    def count(self, s):
        idict, clist = self.__idict, self.__clist
        if s not in idict:
            idict[s] = len(clist)
            clist.append([s, 0])
        ci = idict[s]
        clist[ci][1] += 1
        si = ci
        while si > 0:
            if clist[si - 1][1] < clist[si][1]:
                clist[si - 1], clist[si] = clist[si], clist[si - 1]
                idict[clist[si - 1][0]] = si - 1
                idict[clist[si][0]] = si
                si -= 1
            else:
                break

    def __iter__(self):
        return iter(self.__clist)

結果はこんな感じ。ランダムに一文字アルファベットを割り当てる。

>>> r = RankDict()
>>> for _ in range(100000):
...     r.count(chr(random.randint(97,122)))
... 
>>> for key, count in itertools.islice(r, 10):
...     print '%s : %d' % (key, count)
... 
m : 3997
s : 3936
y : 3888
x : 3882
b : 3880
o : 3870
t : 3868
j : 3865
w : 3864
c : 3864

multiprocessing.Poolがエラーになる人へ

Pythonで並列プログラミングしようとしたときに真っ先に思い浮かぶのが
threadingやmultitprocessingモジュールですよね。

multiprocessingにはPoolという便利なクラスがあって、同時に起動するプロセス数を
制限しながら処理を並列実行できたりします。

例:

import multiprocessing as mulp

def func(self, n):
    return n ** 3

pool = mulp.Pool()
r = pool.map(func, range(10))
print r  # [0, 1, 8, 27, 64, 125, 216, 343, 512, 729]

しかし、この時渡す関数funcがクラスのインスタンスメソッドだったり、匿名関数だったり、関数中で定義した関数だったり、した場合には
エラーが出ます。

import multiprocessing as mulp

class MultiTest(object):
    def func(self, n):
        return n ** 3
        
    def execute(self):
        pool = mulp.Pool()
        r = pool.map(self.func, range(10))
        print r

tester = MultiTest()
tester.execute()  # error!! (pickle error)

ヘルパー関数やヘルパークラスを作ることでこの制限を回避できます。

def tomap(args):
    return getattr(args[0], args[1])(*args[2:])

def toapply(cls, mtd_name, *args, **kwargs):
    return getattr(cls, mtd_name)(*args, **kwargs)

class MulHelper(object):
    def __init__(self, cls, mtd_name):
        self.cls = cls
        self.mtd_name = mtd_name

    def __call__(self, *args, **kwargs):
        return getattr(self.cls, self.mtd_name)(*args, **kwargs)

こんな感じの。これを使って、次のようなサンプルコードを書いたので試してみては。

#!python
# -*- coding: utf-8 -*-

import multiprocessing as mulp
import itertools as itrt

def main():
    tester = MultiTest()
    tester.execute()
    
class MultiTest(object):
    def func(self, n):
        return n ** 3
        
    def execute(self):
        pool = mulp.Pool(4)  # 引数に数字を入れると同時起動数。引数なしなら勝手にcpu情報見に行って最大プロセス数を割り当てる
        # どれかひとつをコメントアウト

        #  失敗する
        # r = pool.map(self.func, range(10))

        #  これも失敗する
        # f = lambda x: toapply(self, 'func', x)
        # r = pool.apply(f,  2)

        # 成功する関数版
        # args = itrt.izip(itrt.repeat(self), itrt.repeat('func'), range(10))
        # r = pool.map(tomap, args)

        # 成功する関数版2
        # r = pool.apply(toapply, (self, 'func', 2))

        # 成功するクラス版
        # r = pool.map(MulHelper(self, 'func'), range(10))

        print 'result:', r

def tomap(args):
    return getattr(args[0], args[1])(*args[2:])

def toapply(cls, mtd_name, *args, **kwargs):
    return getattr(cls, mtd_name)(*args, **kwargs)

class MulHelper(object):
    def __init__(self, cls, mtd_name):
        self.cls = cls
        self.mtd_name = mtd_name

    def __call__(self, *args, **kwargs):
        return getattr(self.cls, self.mtd_name)(*args, **kwargs)

if __name__ == '__main__':
    main()

昨日のままでもなんかできているようなきがするのだが、幾つか気持ち悪い点があったので解決しておく。

  1. 拡張属性あたりで失敗してwarningがでる

しょうがないので拡張属性付きでmountしてやる。やり方はopenpogo環境を入れるときに作ったmount_optと同様のやり方。

まず、バックアップしたいドライブをext3でフォーマットする。

次に、/etc/init.d/mount_optをコピーして、適当に /etc/init.d/mount_tmとでもする。中身はこんな感じ。

#!/bin/sh
#
[ ! -d /tmp/mnt_tm ] && `mkdir /tmp/mnt_tm` || `umount /tmp/mnt_tm`
[ ! -d /var/lock ] && `mkdir /var/lock`
sleep 10
mount -t ext3 -o defaults,acl,user_xattr /dev/sda1 /tmp/mnt_tm
if [ ! -d /tmp/mnt_tm/tm ] 
then
{
 umount /tmp/mnt_tm
 mount -t ext3 -o defaults,acl,user_xattr /dev/sdb1 /tmp/mnt_tm
 if [ ! -d /tmp/mnt_tm/tm ]
 then
 {
  umount /tmp/mnt_tm
  mount -t ext3 -o defaults,acl,user_xattr /dev/sdc1 /tmp/mnt_tm
  if [ ! -d /tmp/mnt_tm/tm ]
  then
  {
   umount /tmp/mnt_tm
   mount -t ext3 -o defaults,acl,user_xattr /dev/sdd1 /tmp/mnt_tm
   if [ ! -d /tmp/mnt_tm/tm ]
   then
   {
   umount /tmp/mnt_tm
   }
   fi
  }
  fi
 }
 fi
}
fi

こいつを/etc/init.d/rcSで呼び出すようにする。

さて、これで/tmp/mnt_tmにいつでもtimemachine用のドライブがマウントされている。
あとは/opt/etc/netatalk/AppleVolumes.defaultをこのパスに変えればOK。

試してみた結果:惨敗orz。warning消えないなー。
どうやらpogoplugのカーネル?が対応していないのかも。オプションつけても認識しない。
まあ処理はうまくいくみたい?だからよしとするか。

副作用でマウントポイントが固定されて楽になった。

OSX Lion + Pogoplug でTimeMachineに挑戦

先日Macbook Air(2011)を買って、環境の移行をシコシコやっていたわけですが、どうせならTimeMachineを手元にあるPogoplugから行いたくて、色々と挑戦していた。

成功したのでメモっておく。

なお、面倒なので忙しい方は素直にTimeCapsuleを買おう。

  1. OpenPogo化する

さすがにこれは適当にググッてもらったほうがいいと思う。

注意点としては、

  • ipkg update時にwgetがしょぼくてエラー
    • /usr/bin/wgetwget.busyboxにrename ... なんでしたのか忘れた。いらないかも。まあ適当にバックアップして。
    • /usr/bin/wget.shを以下の感じで作る
#!/bin/sh
arg=`echo $* | sed -e 's/--passive-ftp//g'`
/bin/busybox wget $arg
ln -s wget.sh wget
  • ipkg updateしてもopen pogoがなくなっててエラー
-bash-3.2# cat /opt/etc/ipkg/armel-feed.conf  
#src cross http://openpogo.com/repo
src cross http://ipkg.nslu2-linux.org/feeds/optware/cs08q1armel/cross/stable
  1. バックアップしたいディスクを予めHFS+でフォーマットする。

ついでにディレクトリをいっこ作っておこう。私は tm という名前のディレクトリを作った。Pogoplugの余計なメタデータがディスクのルートに作られるため。

  1. Pogoplugに差してマウントポイントを調べる

私は4つめだったので/tmp/.cemnt/mnt_sdd1だったお

  1. 依存関係をいっぱいインストールする

netatalk2.2.0-p6をビルドするのだが、たぶん色々と依存関係を解決せねばならない。

avahi, libgcrypt, libssl

あたりを ipkg installで入れよう。もしかしたら他にも必要かもしれない。 私はこの前にlibnslとsambaを入れているので。

netatalkの前に最新のberkeleyDBを入れる。oracleのHPからゲットしてくる。
普通にconfigure, make, make installすればいけたはず。

一点、私の環境では既存のlibdbがあったので、 configure --prefix=/opt/local とした。

  1. netatalk2.2.0-p6をインストールする

https://github.com/jrmithdobbs/netatalk-2-2-0-p6

ソースはここ。

次に、ソースにパッチを当てる。

http://seongbae.blogspot.com/2009/09/turning-pogoplug-into-timemachine.html

ここは参考になる。転載。

Index: afp_options.c
===================================================================
RCS file: /cvsroot/netatalk/netatalk/etc/afpd/afp_options.c,v
retrieving revision 1.45
diff -r1.45 afp_options.c
212a213,216
>     if (strstr(buf, " -noallowroot"))
>         options->flags &= ~OPTION_ALLOW_ROOT;
>     if (strstr(buf, " -allowroot"))
>         options->flags |= OPTION_ALLOW_ROOT;
Index: auth.c
===================================================================
RCS file: /cvsroot/netatalk/netatalk/etc/afpd/auth.c,v
retrieving revision 1.63
diff -r1.63 auth.c
255c255,256
<     if ( pwd->pw_uid == 0 ) { /* don't allow root login */
---
>     if (!(obj->options.flags & OPTION_ALLOW_ROOT)
>         && pwd->pw_uid == 0 ) {       /* don't allow root login */
Index: globals.h
===================================================================
RCS file: /cvsroot/netatalk/netatalk/etc/afpd/globals.h,v
retrieving revision 1.26
diff -r1.26 globals.h
39a40
> #define OPTION_ALLOW_ROOT    (1 << 8)
cvs diff: Diffing nls

上記パッチを頑張って当てよう。
ちなみに、globals.hはatalkdディレクトリの方に移動しているので注意。
また、上記パッチは少し古いので、手動でやろう。確か

#define OPTION_ALLOW_ROOT    (1 << 10)

このあたりが違ったはず。

終わったらビルドする。

./configure --with-bdb=/opt/local --with-ldap=no --prefix=/opt

私の場合のconfigure option。

一応、私の場合の結果を貼っておく。ご参考。

>||

LIBS = -lpthread -L$(top_srcdir)/libatalk
CFLAGS = -I$(top_srcdir)/include -D_U_="__attribute__*1" -g -O2 -I$(top_srcdir)/sys
SSL:
LIBS = -L/opt/lib -L/opt -lcrypto
CFLAGS = -I/opt/include -I/opt/include/openssl
LIBGCRYPT:
LIBS = -L/opt/lib -lgcrypt -lgpg-error
CFLAGS = -I/opt/include -I/home/slug/optware/cs08q1armel/staging/opt/include
BDB:
LIBS = -L/opt/local/lib -ldb-5.2
CFLAGS = -I/opt/local/include/
Configure summary:
Install style:
none
AFP:
AFP 3.x calls activated:
Extended Attributes: ad | sys
CNID:
backends: dbd last tdb
UAMS:
DHX ( SHADOW)
DHX2 ( SHADOW)
RANDNUM ( SHADOW)
passwd ( SHADOW)
guest
Options:
DDP (AppleTalk) support: no
CUPS support: no
SLP support: no
Zeroconf support: yes
tcp wrapper support: no
quota support: no
admin group support: yes
valid shell check: yes
cracklib support: no
dropbox kludge: no
force volume uid/gid: no
Apple 2 boot support: no
ACL support: no
LDAP support: no
|

*1:unused

Python3.2, windows7でvirtualenv(1.6.1)が失敗する件

追記: virtualenv1.6.4では付属のdistributeが最新のものに変更されたため、下のような問題は起きなくなりました。
というかタイミング悪すぎ俺orz


こんなのに半日費やしちゃったよ。

それはさておき、python3.2にvirtualenvを入れようとするとどうも付属のdistributeのバージョンが古すぎて
うまくいかない模様だと言う事がわかった。

しょうがないので、Lib\site-packages\virtualenv.pyを修正して、無理やり最新バージョンを入れられるようにした。

どうもvirtualenvはdistributeのbootstrapファイルを文字列で持っていて、それを直接subprocess -> python -c で呼び出すみたい。

普通にdistribute_setup.py置いといてそれ呼べばいいのにとか思ったり。。。

とにかく、下に書いといたパッチを当てて、

virtualenv --distribute --distribute-version 0.6.19 myvirtualenv

とかやれば指定のバージョンが入るようになった。
なお、site-packages\virtualenv_supportにdistribute-0.6.19.tar.gz突っ込んどけばそれを使うはず(無いとダウンロード)

だれか開発チームに報告しといて:-)

--- virtualenv_old.py	2011-07-11 17:41:13 +0900
+++ virtualenv.py	2011-07-15 11:47:44 +0900
@@ -458,7 +458,7 @@
             return join(dir, filename)
     return filename
 
-def _install_req(py_executable, unzip=False, distribute=False,
+def _install_req(py_executable, unzip=False, distribute=False, distribute_version=None,
                  search_dirs=None, never_download=False):
 
     if search_dirs is None:
@@ -471,7 +471,9 @@
         source = None
     else:
         setup_fn = None
-        source = 'distribute-0.6.16.tar.gz'
+        if distribute_version is None:
+            distribute_version = '0.6.16'
+        source = 'distribute-%s.tar.gz' % distribute_version
         project_name = 'distribute'
         bootstrap_script = DISTRIBUTE_SETUP_PY
         try:
@@ -500,7 +502,8 @@
         os.close(fd)
         cmd = [py_executable, ez_setup]
     else:
-        cmd = [py_executable, '-c', bootstrap_script]
+        cmd = [py_executable, '-c', 
+               re.sub(r'(DEFAULT_VERSION\s+?=\s+?)".*?"', r'\1"%s"' % distribute_version, bootstrap_script)]
     if unzip:
         cmd.append('--always-unzip')
     env = {}
@@ -587,9 +590,9 @@
     _install_req(py_executable, unzip, 
                  search_dirs=search_dirs, never_download=never_download)
 
-def install_distribute(py_executable, unzip=False, 
+def install_distribute(py_executable, unzip=False, version=None,
                        search_dirs=None, never_download=False):
-    _install_req(py_executable, unzip, distribute=True, 
+    _install_req(py_executable, unzip, distribute=True, distribute_version=version,
                  search_dirs=search_dirs, never_download=never_download)
 
 _pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I)
@@ -714,6 +717,12 @@
         help='Use Distribute instead of Setuptools. Set environ variable '
         'VIRTUALENV_USE_DISTRIBUTE to make it the default ')
 
+    parser.add_option(
+        '--distribute-version',
+        dest='distribute_version',
+        metavar='VERSION',
+        help="distribute's version you want to install.")
+
     default_search_dirs = file_search_dirs()
     parser.add_option(
         '--extra-search-dir',
@@ -789,7 +798,7 @@
 
     create_environment(home_dir, site_packages=not options.no_site_packages, clear=options.clear,
                        unzip_setuptools=options.unzip_setuptools,
-                       use_distribute=options.use_distribute or majver > 2,
+                       use_distribute=options.use_distribute or majver > 2, distribute_version=options.distribute_version,
                        prompt=options.prompt,
                        search_dirs=options.search_dirs,
                        never_download=options.never_download)
@@ -868,7 +877,7 @@
 
 
 def create_environment(home_dir, site_packages=True, clear=False,
-                       unzip_setuptools=False, use_distribute=False,
+                       unzip_setuptools=False, use_distribute=False, distribute_version=None,
                        prompt=None, search_dirs=None, never_download=False):
     """
     Creates a new environment in ``home_dir``.
@@ -888,7 +897,7 @@
     install_distutils(home_dir)
 
     if use_distribute or os.environ.get('VIRTUALENV_USE_DISTRIBUTE'):
-        install_distribute(py_executable, unzip=unzip_setuptools, 
+        install_distribute(py_executable, unzip=unzip_setuptools, version=distribute_version,
                            search_dirs=search_dirs, never_download=never_download)
     else:
         install_setuptools(py_executable, unzip=unzip_setuptools, 

python3の組み込み関数bytesはiterable なシーケンスを受け取る!

>>> bytes('a', 'ascii')
b'a'
>>> bytes(97, 'ascii')
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> bytes(97)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> bytes([97], 'ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: encoding or errors without a string argument
>>> bytes([97])
b'a'

ややこしいな。そして美しくない。。。

もちろん、そもそもbytes型は旧strと同じようにシーケンスだから、数値のリストと等価であることはわかる。

だったらchrとordに相当するbyte(int), int(byte)みたいなのを用意してほしかったなあ。