tag:blogger.com,1999:blog-55968613901453876862024-02-08T15:02:48.171+09:00mog projectコンピュータ将棋プログラムの完成を目指して……mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.comBlogger489125tag:blogger.com,1999:blog-5596861390145387686.post-82160421552122758392015-12-31T22:39:00.001+09:002015-12-31T22:42:47.966+09:00Python: Do Not Use Pool.map Method in multiprocessing Module<p>Python: multiprocessing モジュールの Pool.map を使ったときの罠</p>
<p> </p>
<p>Pool.map を使った並行処理をバッチ処理などで実行した際、キーボードによる中断(KeyboardInterrupt)をすると<br /> プロセスがハングアップすることがある。</p>
<h5>コード</h5>
<pre class="brush: python; gutter: true;">#!/usr/bin/env python
from multiprocessing import Pool, current_process
import time
from datetime import datetime
def f(x):
print('[%s] start %d: %s' % (datetime.now(), x, current_process().name))
time.sleep(5)
print('[%s] end %d: %s' % (datetime.now(), x, current_process().name))
return x
pool = Pool(2)
ret = pool.map(f, range(4))
print('[%s] result: %s' % (datetime.now(), ret))
</pre>
<h5>通常の実行例</h5>
<pre class="brush: plain; gutter: false;">[2015-12-31 22:10:02.319094] start 0: PoolWorker-1
[2015-12-31 22:10:02.319267] start 1: PoolWorker-2
[2015-12-31 22:10:07.320538] end 1: PoolWorker-2
[2015-12-31 22:10:07.320538] end 0: PoolWorker-1
[2015-12-31 22:10:07.321345] start 2: PoolWorker-2
[2015-12-31 22:10:07.321434] start 3: PoolWorker-1
[2015-12-31 22:10:12.321765] end 2: PoolWorker-2
[2015-12-31 22:10:12.321765] end 3: PoolWorker-1
[2015-12-31 22:10:12.322721] result: [0, 1, 2, 3]
</pre>
<h5>Ctrl-C を押した場合</h5>
<pre class="brush: plain; gutter: false;">[2015-12-31 22:05:42.108238] start 0: PoolWorker-1
[2015-12-31 22:05:42.109395] start 1: PoolWorker-2
^CProcess PoolWorker-1:
Process PoolWorker-2:
Traceback (most recent call last):
Traceback (most recent call last):
(snip)
return map(*args)
time.sleep(5)
File "./x.py", line 9, in f
KeyboardInterrupt
time.sleep(5)
KeyboardInterrupt
[2015-12-31 22:05:42.631444] start 2: PoolWorker-3
[2015-12-31 22:05:42.632162] start 3: PoolWorker-4
[2015-12-31 22:05:47.632713] end 3: PoolWorker-4
[2015-12-31 22:05:47.632664] end 2: PoolWorker-3</pre>
<p>プログラムが停止せず、kill コマンドでプロセスを終了する必要に迫られる。<br /> 何度 Ctrl-C を押しても駄目である。</p>
<h5>回避策1</h5>
<p>try 節で KeyboardInterrupt をトラップすれば、プログラムのハングアップは防げる。<br /> しかし、中断した後もプログラムが続行してしまう。</p>
<pre class="brush: python; gutter: true;">#!/usr/bin/env python
from multiprocessing import Pool, current_process
import time
from datetime import datetime
def f(x):
try:
print('[%s] start %d: %s' % (datetime.now(), x, current_process().name))
time.sleep(5)
print('[%s] end %d: %s' % (datetime.now(), x, current_process().name))
except KeyboardInterrupt:
pass
return x
pool = Pool(2)
ret = pool.map(f, range(4))
print('[%s] result: %s' % (datetime.now(), ret))
</pre>
<p>実行例</p>
<pre class="brush: plain; gutter: false;">[2015-12-31 22:21:41.647775] start 0: PoolWorker-1
[2015-12-31 22:21:41.647928] start 1: PoolWorker-2
^C[2015-12-31 22:21:42.716623] start 2: PoolWorker-2
[2015-12-31 22:21:42.716755] start 3: PoolWorker-1
[2015-12-31 22:21:47.717815] end 2: PoolWorker-2
[2015-12-31 22:21:47.717832] end 3: PoolWorker-1
Traceback (most recent call last):
(snip)
waiter.acquire()
KeyboardInterrupt</pre>
<h5>回避策2</h5>
<p>map の代わりに map_async を使えば、問題を回避できる。この場合は即座にプログラムが終了する。<br /> ただし、結果を利用する際には get メソッドとともにタイムアウトの時間(秒数)を指定する必要がある。</p>
<pre class="brush: python; gutter: true;">#!/usr/bin/env python
from multiprocessing import Pool, current_process
import time
from datetime import datetime
def f(x):
print('[%s] start %d: %s' % (datetime.now(), x, current_process().name))
time.sleep(5)
print('[%s] end %d: %s' % (datetime.now(), x, current_process().name))
return x
pool = Pool(2)
p = pool.map_async(f, range(4))
ret = p.get(86400)
print('[%s] result: %s' % (datetime.now(), ret))
</pre>
<p>実行例</p>
<pre class="brush: plain; gutter: false;">[2015-12-31 22:24:32.482984] start 0: PoolWorker-1
[2015-12-31 22:24:32.483816] start 1: PoolWorker-2
^CTraceback (most recent call last):
(snip)
time.sleep(5)
KeyboardInterrupt</pre>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="http://bugs.python.org/issue8296">Issue 8296: multiprocessing.Pool hangs when issuing KeyboardInterrupt - Python tracker</a></li>
</ul>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-90811369589387321872015-12-21T05:14:00.001+09:002015-12-21T05:14:19.501+09:00color-ssh: Runs Remote Commands, Colorfully!<p>color-ssh: カラフルなリモートコマンドの実行ツール</p>
<p> </p>
<p>color-ssh というコマンドライン・ツールを作った。</p>
<ul>
<li><a href="https://github.com/mogproject/color-ssh">mogproject/color-ssh</a></li>
</ul>
<p> </p>
<h4>インストール</h4>
<pre class="brush: plain; gutter: false;"># pip install color-ssh</pre>
<ul>
<li>環境によっては「sudo」コマンドが必要</li>
<li>バージョンアップする場合は、「pip install --upgrade color-ssh」</li>
</ul>
<p> </p>
<p>「color-cat」と「color-ssh」という 2個のコマンドがインストールされる。<br /> 以下に、基本的な使い方を説明する。</p>
<pre class="brush: plain; gutter: false;">$ color-cat --version
color-cat 0.1.0
$ color-ssh --version
color-ssh 0.1.0</pre>
<p> </p>
<h4>color-cat</h4>
<p>UNIX/Linux の cat コマンドにターミナル上で色を付けるだけのツール。</p>
<p><a title="x" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDn99-qXmZlQr9QZF3XxqM55QGOCvTQRYy8kYnVcLJ7PmzwtVA9T6wneTuZXVWjzSP7DFjOiVZidlr04OiTrq6PwTfkjywPu-KqP-WWtN-uEKEhbeZG3TV0lvfLqnNA8BuMrNfhqu3WOfx/?imgmax=800"><img title="x.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDn99-qXmZlQr9QZF3XxqM55QGOCvTQRYy8kYnVcLJ7PmzwtVA9T6wneTuZXVWjzSP7DFjOiVZidlr04OiTrq6PwTfkjywPu-KqP-WWtN-uEKEhbeZG3TV0lvfLqnNA8BuMrNfhqu3WOfx/?imgmax=800" alt="x" width="400" height="auto" /></a></p>
<ul>
<li>引数なしで実行すれば、標準入力から読み取った文字列が、ターミナルに赤色で表示される。</li>
<li>「-l」オプションでラベルを指定。各行の左側に反転色で表示される。
<ul>
<li>ラベルを変えれば色も変わる。<br /> ラベルの文字列によって、色が一意に決まるというのが最大の特徴。</li>
</ul>
</li>
<li>「-c」オプションで色を直接指定することも可能。</li>
<li>「-s」オプションでセパレータ文字列も変えられる。</li>
<li>もちろん、通常の cat コマンド同様、引数にファイルパスを指定すれば、その内容を全て表示する。</li>
<li>「color-cat --help」でヘルプを表示。</li>
</ul>
<p> </p>
<h4>color-ssh</h4>
<p>ssh コマンドの出力を色付きで表示するためのツール。<br /> 内部では color-cat コマンドが実行されている。</p>
<p><a title="x" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisNvHJDBdmP06hHybZ6Q_4H10v_qkh3SQ0_KSuUZ-w30yqxMTmx6ZmCbf_38wxzNkA_PXe1Fkjfne3HpGUJP3Qhpz1Zpo5X_YxFrAX5HCT3f3tcp-tAGPXo24wD0aOFsa2VPyUY3HOGUG9/?imgmax=800"><img title="x.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisNvHJDBdmP06hHybZ6Q_4H10v_qkh3SQ0_KSuUZ-w30yqxMTmx6ZmCbf_38wxzNkA_PXe1Fkjfne3HpGUJP3Qhpz1Zpo5X_YxFrAX5HCT3f3tcp-tAGPXo24wD0aOFsa2VPyUY3HOGUG9/?imgmax=800" alt="x" width="400" height="auto" /></a></p>
<ul>
<li>ssh コマンドを使ってリモートサーバ上でコマンドを実行し、その出力を色付きで表示する。</li>
<li>サーバの名前が color-cat のラベルとして利用される。そのため同じサーバは同じ色で出力される。</li>
<li>標準エラー出力はセパレータが「+」に変わる。</li>
<li>複数のサーバ上で、同じコマンドを同時に実行することが可能。(parallel-ssh と同じような機能)
<ul>
<li>ホスト名のリストは、「-h」オプションでファイルから読み込むか、「-H」オプションで文字列として指定する。</li>
</ul>
</li>
<li>「-p」オプションで多重度を制限可能。</li>
<li>「color-ssh --help」でヘルプを表示。</li>
</ul>
<p> </p>
<h5>コマンドライン引数の「分配」</h5>
<p>「--distribute」オプションを指定すれば、引数を各サーバに分割・分配して実行することができる。<br />
(これをやりたかった)</p>
<a href="https://lh3.googleusercontent.com/-RFp8Xy2czTk/VncKIWoYacI/AAAAAAAAAqI/zsGbfajNlMA/x.png?imgmax=800" title="x"><img src="https://lh3.googleusercontent.com/-RFp8Xy2czTk/VncKIWoYacI/AAAAAAAAAqI/zsGbfajNlMA/x.png?imgmax=800" alt="x" title="x.png" width="400" height="auto" /></a>
<p>上記の例では、「a」「b」「c」「d」「e」という 5個の引数を、各サーバにほぼ均等になるように割り当てて、そのコマンドを並行実行する。</p><p>
server-1 では「echo xxx a b c」<br />
server-2 では「echo xxx d e」</p>
<p>というコマンドが同時に実行されている。</p>
<p>この機能は、多数の入力ファイルと複数のサーバがある場合、手軽に仕事を分割するのに役立つと思う。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-1201320113726209362015-12-18T23:32:00.001+09:002015-12-18T23:32:37.588+09:00How to Handle Binary-data stdin/stdout and Command-line Arguments in Python3<p>Python: 標準入出力およびコマンドライン引数でバイナリデータを取り扱う方法</p>
<p> </p>
<h4>Python2 の場合</h4>
<p>str = bytes なので、普通に書けばバイナリデータにも対応できる。</p>
<pre class="brush: python; gutter: true;" title="bin_stdin.py">import sys
sys.stdout.write('### sys.argv\n')
for arg in sys.argv:
sys.stdout.write(arg)
sys.stdout.write('\n')
sys.stderr.write('### sys.stdin\n')
for line in iter(sys.stdin.readline, ''):
sys.stderr.write(line.rstrip())
sys.stderr.write('\n')
</pre>
<ul>
<li>実行例 (?? の箇所は表示非対応)</li>
</ul>
<pre class="brush: plain; gutter: false;">$ echo $'abc\nあいう\n\xff\xfe' | python2 ./bin_stdin.py abc あいう $'\xff\xfe'
### sys.argv
./bin_stdin.py
abc
あいう
??
### sys.stdin
abc
あいう
??</pre>
<p> </p>
<h4>Python3 の場合</h4>
<p>上記のコードはエンコードエラーになる。<br /> これは、Python3 の sys.stdout.write が str = unicode を引数に取るため。</p>
<pre class="brush: plain; gutter: false;">$ echo $'abc\nあいう\n\xff\xfe' | python3 ./bin_stdin.py abc あいう $'\xff\xfe'
### sys.argv
./bin_stdin.py
abc
あいう
Traceback (most recent call last):
File "./bin_stdin.py", line 5, in
sys.stdout.write(arg)
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcff' in position 0: surrogates not allowed</pre>
<p> </p>
<h5>ポイント</h5>
<ul>
<li>1. sys.stdXXX.buffer を利用する</li>
<li>2. sys.args の要素を os.fsencode でエンコードする
<ul>
<li>ただしOptionParserなどのパース処理はエンコード前に適用しないと正しく処理されない模様</li>
</ul></li>
</ul>
<p>以下のようなコードを書けば、Python2/3 両方に対応できる。 (flush は必ずしも必須ではない)</p>
<pre class="brush: python; gutter: true;" title="bin_stdin.py">import sys
import os
PY3 = sys.version_info >= (3, )
stdin = sys.stdin.buffer if PY3 else sys.stdin
stdout = sys.stdout.buffer if PY3 else sys.stdout
stderr = sys.stderr.buffer if PY3 else sys.stderr
stdout.write(b'### sys.argv\n')
for arg in sys.argv:
stdout.write(os.fsencode(arg) if PY3 else arg)
stdout.write(b'\n')
stdout.flush()
stderr.write(b'### sys.stdin\n')
for line in iter(stdin.readline, b''):
stderr.write(line.rstrip())
stderr.write(b'\n')
stderr.flush()</pre>
<ul>
<li>実行例</li>
</ul>
<pre class="brush: plain; gutter: false;">$ echo $'abc\nあいう\n\xff\xfe' | python3 ./bin_stdin.py abc あいう $'\xff\xfe'
### sys.argv
./bin_stdin.py
abc
あいう
??
### sys.stdin
abc
あいう
??</pre>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-86583455271865202652015-12-17T00:33:00.001+09:002015-12-17T00:33:06.389+09:00How to Color the Output from SSH Commands<p>SSH の出力をカラフルにする方法</p>
<p> </p>
<p>SSH に限ったことではないが、ターミナルに表示されるテキストを目的に応じて着色したい場合、<br /> パイプラインと sed を使うのが一番簡単だ。</p>
<pre class="brush: plain; gutter: false;">$ ssh server-1 'python -c "import this"' \
| sed -e $'s/^\\(.*\\)$/\e[96mserver-1\e[0m|\e[36m\\1\e[0m/'</pre>
<p>出力は以下のようになる。<br /> <a title="x" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhpUWtNCV1frFnXUa8gy9dA_Yw2iv4Wiwq58awkY36O9ie5HLSsB2-nMdYlqH6qbLZcliXlGPD6VJgLxzkbb2b-5n_RzfgZNVNWSSLlZ_rl-rBciNX4fESYBgiB-xoc9d7NPKr8YsKBu1T/?imgmax=800"><img title="x.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhpUWtNCV1frFnXUa8gy9dA_Yw2iv4Wiwq58awkY36O9ie5HLSsB2-nMdYlqH6qbLZcliXlGPD6VJgLxzkbb2b-5n_RzfgZNVNWSSLlZ_rl-rBciNX4fESYBgiB-xoc9d7NPKr8YsKBu1T/?imgmax=800" alt="x" width="400" height="auto" /></a></p>
<p>\e[96m や \e[36m の部分を変更すれば、色が変わる。</p>
<p> </p>
<p>この応用として、複数のサーバに対して SSH をバックグラウンドで実行し(コマンドラインの末尾に「&」を付ける)<br />
最後に wait コマンドで処理を待つようなシェルを書けば、サーバに応じた色のアウトプットをリアルタイムに観察できる。<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5z0COoAjxe7wwyPJkdicL7QOXzgcdQe5LZLZvZABPExWABU6UufO4B6kA4Ws8xlW5YCY8gXA4zySmBS7KR7_1kSn4b77OLhLjKA63h-UAdgDF4RMtlhpMng0arjYyX3ga2MWK2oyvCYIH/?imgmax=800" title="y"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5z0COoAjxe7wwyPJkdicL7QOXzgcdQe5LZLZvZABPExWABU6UufO4B6kA4Ws8xlW5YCY8gXA4zySmBS7KR7_1kSn4b77OLhLjKA63h-UAdgDF4RMtlhpMng0arjYyX3ga2MWK2oyvCYIH/?imgmax=800" alt="y" title="y.png" width="400" height="auto" /></a></p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-2932964952700529612015-12-16T04:36:00.001+09:002015-12-16T04:36:29.357+09:00Manipulating Many Servers by Using parallel-ssh<p>parallel-ssh を使って複数サーバを同時に操作する</p>
<p> </p>
<h4>tl; dr</h4>
<ul>
<li>2台〜100台程度のサーバに対して、同じオペレーションを同時に実行したい</li>
<li>parallel-ssh (pssh) の各種コマンドを使うと便利</li>
<li>ただし、リモートサーバで sudo が必要な場合は Ansible 等の他ツールが必要</li>
</ul>
<p> </p>
<h4>parallel-ssh のセットアップ</h4>
<p> </p>
<h5>インストール</h5>
<p> </p>
<a href="https://code.google.com/p/parallel-ssh/">parallel-ssh - PSSH: Parallel SSH Tools - Google Project Hosting</a>
<ul>
<li>Mac<br />
<pre class="brush: plain; gutter: false;">$ brew install pssh</pre>
</li>
<li>RHEL系<br />
<pre class="brush: plain; gutter: false;"># yum install pssh</pre>
</li>
</ul>
<h5>ホストリストの準備</h5>
<ul>
<li>任意のパスにテキストファイルを作成<br />
<pre class="brush: plain; gutter: false;" title="~/hosts">server-1
server-2
server-3 root</pre>
</li>
<li>1行ずつ、接続先サーバのホスト名またはIPアドレスを記述</li>
<li>ログインユーザの指定も可能</li>
</ul>
<p> </p>
<h4>目的別コマンド実行例</h4>
<p> </p>
<h5>SSH 認証のための設定</h5>
<p>parallel-ssh を使うには、各サーバに SSH で接続できることが前提となる。<br />ssh-copy-id コマンドを利用して、~/.ssh/id_rsa の内容を登録する場合は以下。</p>
<pre class="brush: plain; gutter: false;">$ for h in $(cat ~/hosts); do ssh-copy-id $h; done</pre>
<p> </p>
<h5>pssh の使い方</h5>
<ul>
<li>基本形<br />
<pre class="brush: plain; gutter: false; highlight:1">$ pssh -h ~/hosts -i 'hostname'
[1] 04:09:13 [SUCCESS] server-1
server-1
[2] 04:09:13 [SUCCESS] server-2
server-2</pre>
</li>
<li>ホスト名を直接指定<br />
<pre class="brush: plain; gutter: false; highlight:1">$ pssh -H server-1 -H server-2 -i 'hostname'
[1] 04:09:13 [SUCCESS] server-1
server-1
[2] 04:09:13 [SUCCESS] server-2
server-2</pre>
</li>
<li>タイムアウトを無制限に<br />
<pre class="brush: plain; gutter: false; highlight:1">$ pssh -h ~/hosts -t 0 -i 'hostname'
[1] 04:09:13 [SUCCESS] server-1
server-1
[2] 04:09:13 [SUCCESS] server-2
server-2</pre>
</li>
<li>アウトプットをすぐに表示<br />
<pre class="brush: plain; gutter: false; highlight:1">$ pssh -h ~/hosts -P 'hostname'
server-1: server-1
[1] 04:13:28 [SUCCESS] server-1
server-2: server-2
[2] 04:13:28 [SUCCESS] server-2
</pre>
</li>
<li>リモートでバックグラウンド実行<br />
<pre class="brush: plain; gutter: false; highlight:1">$ pssh -h ~/hosts -i 'nohup your_command > /path/to/output 2>&1 </dev/null &'
[1] 04:13:28 [SUCCESS] server-1
[2] 04:13:28 [SUCCESS] server-2
</pre>
バッファの影響で、標準出力の出力先ファイルがリアルタイムに更新されない場合がある。<br /> 対応にはハックが必要: <a href="http://stackoverflow.com/questions/7610922/redirect-nohup-stdout-and-flush">linux - redirect nohup stdout and flush - Stack Overflow</a></li>
</ul>
<p> </p>
<h5>ファイル転送</h5>
<ul>
<li>ローカルからリモートに転送<br />
<pre class="brush: plain; gutter: false;">$ prsync -h ~/hosts -av /data/input/ /data/input/</pre>
<ul>
<li>パスの末尾にスラッシュを付けると、ディレクトリ間の同期となる。(rsync の仕様)</li>
<li>-z (圧縮オプション) は付けると CPU がボトルネックになって大幅に性能が劣化する場合がある。ナローバンドでなければ、付けないほうが良さそうだ。</li>
</ul>
</li>
<li>リモートからローカルに転送<br />
<pre class="brush: plain; gutter: false;">$ pssh -h ~/hosts -p 4 -i 'rsync -e "ssh -o StrictHostKeyChecking=no" -av \
/data/output/ server-1:/data/output/'</pre>
<ul>
<li>全サーバから server-1 に集約する例</li>
<li>-p オプションを指定して、並列度を制限している。<br />
こうしないと、コネクションが多すぎて接続できない場合がある。</li>
<li>rsync -e "ssh -o ..." という書式で SSH オプションを指定。<br />
ここではホストキーのチェックを回避している。</li>
</ul>
</li>
</ul>
<p> </p>
<h5>リモートサーバで sudo が必要な場合</h5>
<ul>
<li>Ansible で代用
<pre class="brush: plain; gutter: false;">$ ansible -i ~/hosts all --sudo -K \
-a 'bash -c "cd /path/to/your/app && make install"'
</pre>
</li>
</ul>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="https://snipsnaptmae.wordpress.com/2012/09/17/parallel-ssh-glide-note-52734/">Parallel sshで複数のホストへ同時にコマンドを実行する | Glide Note – グライドノート | snip-snap</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-39993091776235039932015-12-08T02:35:00.001+09:002015-12-08T03:23:31.494+09:00Python: How to Execute Parallel Processing using multiprocessing.Pool<p>Python: multiprocessing.Pool を使った並列処理の実行</p>
<p> </p>
<p>Python で並列処理を行う場合、自分でスレッドを書くこともできるが、<a href="https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%97%E3%83%AA%E3%82%BF%E3%83%AD%E3%83%83%E3%82%AF">グローバルインタプリタロック (GIL)</a> の制約を受けて意図しない結果に陥ることが少なくない。</p>
<p>multithreading モジュールの Pool を利用するのが最も簡単かつ安全である。</p>
<p> </p>
<h4>基本形</h4>
<p>引数を1個取る関数を定義し、Pool#map を実行する。</p>
<pre class="brush: python; gutter: true;">import time
from datetime import datetime
from multiprocessing import Pool, current_process
def f(x):
proc_name = current_process().name
print('(%s) [%s] Started: x=%d' % (proc_name, datetime.now(), x))
time.sleep(1)
print('(%s) [%s] Ended : x=%d' % (proc_name, datetime.now(), x))
return x * x
pool = Pool(4)
print(pool.map(f, range(8)))
</pre>
<p>尚、Python 3.4 以降なら with 句を使った表現もできる。</p>
<p>実行結果は以下のようになるはずだ。</p>
<pre class="brush: plain; gutter: false;">(PoolWorker-1) [2015-12-08 01:49:03.666114] Started: x=0
(PoolWorker-2) [2015-12-08 01:49:03.666246] Started: x=1
(PoolWorker-3) [2015-12-08 01:49:03.666400] Started: x=2
(PoolWorker-4) [2015-12-08 01:49:03.666620] Started: x=3
(PoolWorker-1) [2015-12-08 01:49:04.667421] Ended : x=0
(PoolWorker-3) [2015-12-08 01:49:04.667421] Ended : x=2
(PoolWorker-2) [2015-12-08 01:49:04.667423] Ended : x=1
(PoolWorker-4) [2015-12-08 01:49:04.667511] Ended : x=3
(PoolWorker-3) [2015-12-08 01:49:04.668070] Started: x=4
(PoolWorker-2) [2015-12-08 01:49:04.668151] Started: x=5
(PoolWorker-1) [2015-12-08 01:49:04.668282] Started: x=6
(PoolWorker-4) [2015-12-08 01:49:04.668382] Started: x=7
(PoolWorker-3) [2015-12-08 01:49:05.669206] Ended : x=4
(PoolWorker-2) [2015-12-08 01:49:05.669206] Ended : x=5
(PoolWorker-4) [2015-12-08 01:49:05.669218] Ended : x=7
(PoolWorker-1) [2015-12-08 01:49:05.669230] Ended : x=6
[0, 1, 4, 9, 16, 25, 36, 49]</pre>
<p>実行中はプロセスがフォークされているので、特にメモリ使用量は気にかける必要がある。</p>
<p> </p>
<h4>複数のパラメータを取る</h4>
<p>タプルを渡して、ワーカー側で分解するのが常套手段。</p>
<pre class="brush: python; gutter: true;">import time
from datetime import datetime
from multiprocessing import Pool, current_process
def f(args):
x, y = args
proc_name = current_process().name
print('(%s) [%s] Started: x=%d, y=%d' % (proc_name, datetime.now(), x, y))
time.sleep(1)
print('(%s) [%s] Ended : x=%d, y=%d' % (proc_name, datetime.now(), x, y))
return x * y
pool = Pool(4)
print(pool.map(f, zip(range(8), range(10, 18))))
</pre>
<p>実行例</p>
<pre class="brush: plain; gutter: false;">(PoolWorker-1) [2015-12-08 01:55:01.700005] Started: x=0, y=10
(PoolWorker-2) [2015-12-08 01:55:01.700078] Started: x=1, y=11
(PoolWorker-3) [2015-12-08 01:55:01.700196] Started: x=2, y=12
(PoolWorker-4) [2015-12-08 01:55:01.700463] Started: x=3, y=13
(PoolWorker-3) [2015-12-08 01:55:02.701283] Ended : x=2, y=12
(PoolWorker-2) [2015-12-08 01:55:02.701275] Ended : x=1, y=11
(PoolWorker-1) [2015-12-08 01:55:02.701261] Ended : x=0, y=10
(PoolWorker-4) [2015-12-08 01:55:02.701293] Ended : x=3, y=13
(PoolWorker-3) [2015-12-08 01:55:02.701914] Started: x=4, y=14
(PoolWorker-1) [2015-12-08 01:55:02.702046] Started: x=5, y=15
(PoolWorker-2) [2015-12-08 01:55:02.702165] Started: x=6, y=16
(PoolWorker-4) [2015-12-08 01:55:02.702361] Started: x=7, y=17
(PoolWorker-1) [2015-12-08 01:55:03.703123] Ended : x=5, y=15
(PoolWorker-3) [2015-12-08 01:55:03.703106] Ended : x=4, y=14
(PoolWorker-4) [2015-12-08 01:55:03.703131] Ended : x=7, y=17
(PoolWorker-2) [2015-12-08 01:55:03.703140] Ended : x=6, y=16
[0, 11, 24, 39, 56, 75, 96, 119]</pre>
<p> </p>
<h4>ワーカーのスタックトレースを出力する</h4>
<p>何らかの例外が発生する場合を考える。</p>
<pre class="brush: python; gutter: true;">import time
from datetime import datetime
from multiprocessing import Pool, current_process
def f(args):
x, y = args
proc_name = current_process().name
print('(%s) [%s] Started: x=%d, y=%d' % (proc_name, datetime.now(), x, y))
time.sleep(1)
print('(%s) [%s] Ended : x=%d, y=%d' % (proc_name, datetime.now(), x, y))
return y / x
pool = Pool(4)
print(pool.map(f, zip(range(8), range(10, 18))))
</pre>
<p>そのままだと、スタックトレースの内容が分かりづらい。</p>
<pre class="brush: plain; gutter: false;">(PoolWorker-1) [2015-12-08 01:59:54.088732] Started: x=0, y=10
(PoolWorker-2) [2015-12-08 01:59:54.088835] Started: x=1, y=11
(PoolWorker-3) [2015-12-08 01:59:54.088960] Started: x=2, y=12
(PoolWorker-4) [2015-12-08 01:59:54.089304] Started: x=3, y=13
(PoolWorker-3) [2015-12-08 01:59:55.090067] Ended : x=2, y=12
(PoolWorker-2) [2015-12-08 01:59:55.090095] Ended : x=1, y=11
(PoolWorker-1) [2015-12-08 01:59:55.090049] Ended : x=0, y=10
(PoolWorker-4) [2015-12-08 01:59:55.090160] Ended : x=3, y=13
(PoolWorker-2) [2015-12-08 01:59:55.090750] Started: x=4, y=14
(PoolWorker-3) [2015-12-08 01:59:55.090926] Started: x=5, y=15
(PoolWorker-4) [2015-12-08 01:59:55.091020] Started: x=6, y=16
(PoolWorker-1) [2015-12-08 01:59:55.091336] Started: x=7, y=17
Traceback (most recent call last):
File "xxx.py", line 14, in
print(pool.map(f, zip(range(8), range(10, 18))))
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/multiprocessing/pool.py", line 251, in map
return self.map_async(func, iterable, chunksize).get()
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/multiprocessing/pool.py", line 558, in get
raise self._value
ZeroDivisionError: integer division or modulo by zero</pre>
<p>デコレータでスタックトレースを表示してみる。副産物として、関数 f が複数のパラメータを取れるようになった。</p>
<pre class="brush: python; gutter: true;">import time
from datetime import datetime
from multiprocessing import Pool, current_process
import traceback
def with_stacktrace(func):
import functools
@functools.wraps(func)
def wrapper(args):
try:
return func(*args)
except:
proc_name = current_process().name
for line in traceback.format_exc().splitlines():
print('[TRACE:%s] %s' % (proc_name, line))
raise
return wrapper
@with_stacktrace
def f(x, y):
proc_name = current_process().name
print('(%s) [%s] Started: x=%d, y=%d' % (proc_name, datetime.now(), x, y))
time.sleep(1)
print('(%s) [%s] Ended : x=%d, y=%d' % (proc_name, datetime.now(), x, y))
return y / x
pool = Pool(4)
print(pool.map(f, zip(range(8), range(10, 18))))
</pre>
<p>実行例</p>
<pre class="brush: plain; gutter: false;highlight:[12,13,14,15,16,17];">(PoolWorker-1) [2015-12-08 02:31:36.959575] Started: x=0, y=10
(PoolWorker-2) [2015-12-08 02:31:36.959657] Started: x=1, y=11
(PoolWorker-3) [2015-12-08 02:31:36.959721] Started: x=2, y=12
(PoolWorker-4) [2015-12-08 02:31:36.960174] Started: x=3, y=13
(PoolWorker-4) [2015-12-08 02:31:37.960911] Ended : x=3, y=13
(PoolWorker-2) [2015-12-08 02:31:37.960904] Ended : x=1, y=11
(PoolWorker-3) [2015-12-08 02:31:37.960920] Ended : x=2, y=12
(PoolWorker-1) [2015-12-08 02:31:37.960888] Ended : x=0, y=10
(PoolWorker-2) [2015-12-08 02:31:37.961423] Started: x=4, y=14
(PoolWorker-3) [2015-12-08 02:31:37.961634] Started: x=5, y=15
(PoolWorker-4) [2015-12-08 02:31:37.961724] Started: x=6, y=16
[TRACE:PoolWorker-1] Traceback (most recent call last):
[TRACE:PoolWorker-1] File "xxx.py", line 9, in f
[TRACE:PoolWorker-1] return func(*args)
[TRACE:PoolWorker-1] File "xxx.py", line 23, in f
[TRACE:PoolWorker-1] return y / x
[TRACE:PoolWorker-1] ZeroDivisionError: integer division or modulo by zero
(PoolWorker-1) [2015-12-08 02:31:37.962384] Started: x=7, y=17
Traceback (most recent call last):
File "xxx.py", line 26, in
print(pool.map(f, zip(range(8), range(10, 18))))
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/multiprocessing/pool.py", line 251, in map
return self.map_async(func, iterable, chunksize).get()
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/multiprocessing/pool.py", line 558, in get
raise self._value
ZeroDivisionError: integer division or modulo by zero</pre>
<p>これでデバッグが捗る。</p>
<p> </p>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="http://docs.python.jp/2.6/library/multiprocessing.html">16.6. multiprocessing — プロセスベースの “並列処理” インタフェース — Python 2.6ja2 documentation</a></li>
<li><a href="http://mocobeta-backup.tumblr.com/post/87477506807/python-23-multiprocessing-pool">moco(beta)'s backup: Python 標準ライブラリ探訪 (23) ~ multiprocessing.Pool 編 ~</a></li>
<li><a href="http://nedbatchelder.com/blog/200711/rethrowing_exceptions_in_python.html">Ned Batchelder: Re-throwing exceptions in Python</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-1885270930457897542015-11-30T02:31:00.001+09:002015-12-01T10:32:17.774+09:00C++/Python: Writing Custom Converters in Boost.Python<p>C++/Python: Boost.Python でカスタムコンバーターを作る</p>
<p> </p>
<p>C++ のクラスを Python で扱うとき、std::pair などのクラスはデフォルトでは型変換が行われない。自分でコンバーターを書く必要がある。</p>
<p> </p>
<p>以下はコードの例。C++11のラムダ式を利用したら綺麗にまとまった。</p>
<pre class="brush: cpp; gutter: true;">#include <boost/python.hpp>
#include "your_code.hpp"
namespace yourmodulename {
namespace py = boost::python;
/*
* Converter for result set (std::vector of std::pair => pylist of pytuple)
*/
namespace detail {
template <typename T, typename U>
struct pair_vector_to_tuple_list {
static PyObject* convert(std::vector<std::pair<T, U> > const& ps) {
py::list ret;
for (auto it: ps) ret.append(py::make_tuple(it.first, it.second));
return py::incref(ret.ptr());
}
static PyTypeObject const* get_pytype() { return &PyList_Type; }
};
}
template <typename T, typename U>
void expose_pair_vector_to_tuple_list() {
py::to_python_converter<std::vector<std::pair<T, U> >, detail::pair_vector_to_tuple_list<T, U>, true>();
}
/*
* Converter for list parameter (pylist => std::vector)
*/
template <typename T>
void expose_pylist_to_vector() {
typedef std::vector<T> VT;
auto convertible = [](PyObject* obj_ptr) -> void* { return PySequence_Check(obj_ptr) ? obj_ptr : NULL; };
auto construct = [](PyObject* obj_ptr, py::converter::rvalue_from_python_stage1_data* data) {
VT* storage = new (reinterpret_cast<py::converter::rvalue_from_python_storage<VT>*>(data)->storage.bytes) VT();
for (py::ssize_t i = 0, l = PySequence_Size(obj_ptr); i < l; ++i) {
storage->push_back(py::extract<typename boost::range_value<VT>::type>(PySequence_GetItem(obj_ptr, i)));
}
data->convertible = storage;
};
py::converter::registry::push_back(convertible, construct, py::type_id<VT>());
}
}
BOOST_PYTHON_MODULE(yourmodulename) {
using namespace boost::python;
using namespace yourmodulename;
// expose converters
expose_pair_vector_to_tuple_list<int, double>();
expose_pylist_to_vector<int>();
// expose classes
...
}
</pre>
<p>なかなかに魔力を吸い取られそうなコードだ。</p>
<p> </p>
<p> </p>
<h5>Related Posts</h5>
<ul>
<li><a href="http://mogproject.blogspot.jp/2015/01/cpython-diving-into-boostpython.html">mog project: C++/Python: Diving Into Boost.Python</a></li>
<li><a href="http://mogproject.blogspot.jp/2015/01/cpython-how-to-run-boostpython-module.html">mog project: C++/Python: How to run Boost.Python module using setup.py with Python3.4 on Mac</a></li>
</ul>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="https://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/">How to write boost.python converters | Misspent</a></li>
<li><a href="http://d.hatena.ne.jp/moriyoshi/20091214/1260779899">Boost.Python の機能をざっと紹介してみる - muddy brown thang</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-67493438353408956832015-11-27T23:30:00.001+09:002015-11-27T23:52:07.253+09:00Top N Sorter in Python<p>Python: トップNソートの実装</p>
<p> </p>
<p>ソート可能な iterable に対して、降順の上位 N件のソート結果を取得したい。</p>
<p>heapq を使えば効率が良さそうだ。</p>
<p> </p>
<h4>コード</h4>
<pre class="brush: python; gutter: true;">import heapq
class TopNSorter(object):
def __init__(self, limit):
self._heap = []
self.limit = limit
def insert(self, item):
if len(self._heap) == self.limit:
heapq.heappushpop(self._heap, item)
else:
heapq.heappush(self._heap, item)
def result(self):
h = self._heap[:]
buf = []
while h:
buf.insert(0, heapq.heappop(h))
return buf</pre>
<p> </p>
<h4>実行例</h4>
<pre class="brush: python; gutter: false;">>>> s = TopNSorter(5)
>>> s.result()
[]
>>> s.insert(10)
>>> s.result()
[10]
>>> s.insert(20)
>>> s.result()
[20, 10]
>>> s.insert(10)
>>> s.result()
[20, 10, 10]
>>> s.insert(5)
>>> s.result()
[20, 10, 10, 5]
>>> s.insert(30)
>>> s.result()
[30, 20, 10, 10, 5]
>>> s.insert(15)
>>> s.result()
[30, 20, 15, 10, 10]
>>> s.insert(10)
>>> s.result()
[30, 20, 15, 10, 10]
>>> s.insert(25)
>>> s.result()
[30, 25, 20, 15, 10]
>>> s.insert(1)
>>> s.result()
[30, 25, 20, 15, 10]</pre>
<p> </p>
<h5>Related Posts</h5>
<ul>
<li><a href="http://mogproject.blogspot.jp/2014/08/top-n-sorter-in-scala.html">mog project: Top N Sorter in Scala</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-62905114964528484852015-11-22T02:03:00.001+09:002015-11-22T02:11:27.221+09:00Finding Community Structure in a Big Graph<p>C++: ビッググラフにおけるコミュニティ抽出のメモ</p>
<p> </p>
<h4>手法</h4>
<p>ネットワークにおけるコミュニティ抽出 (グラフのクラスタリング) にはいくつかの手法がある。</p>
<p>こちらを参照 => <a href="http://www.slideshare.net/kztakemoto/r-seminar-on-igraph/50">R seminar on igraph</a></p>
<ul>
<li>Non-overlapping (strict partition)
<ul>
<li>辺媒介性を用いた手法<br /> <a href="http://arxiv.org/abs/cond-mat/0112110">[cond-mat/0112110] Community structure in social and biological networks</a></li>
<li>貪欲 (greedy) アルゴリズムを用いた手法<br /> <a href="http://arxiv.org/abs/cond-mat/0408187">[cond-mat/0408187] Finding community structure in very large networks</a></li>
<li>スペクトル最適化法<br /> <a href="http://arxiv.org/abs/physics/0602124">[physics/0602124] Modularity and community structure in networks</a></li>
<li>焼きなまし法 (Simulated annealing)<br /> <a href="http://arxiv.org/abs/cond-mat/0603718">[cond-mat/0603718] Statistical Mechanics of Community Detection</a></li>
</ul>
</li>
<li>Overlapping
<ul>
<li>Cique percolation<br /> <a href="http://arxiv.org/abs/physics/0506133">[physics/0506133] Uncovering the overlapping community structure of complex networks in nature and society</a></li>
<li>Edge partition<br /> <a href="http://arxiv.org/abs/0903.2181">[0903.2181] Line Graphs, Link Partitions and Overlapping Communities</a></li>
<li>Link communities<br /> <a href="http://arxiv.org/abs/0903.3178">[0903.3178] Link communities reveal multiscale complexity in networks</a></li>
<li>Overlapping cluster generator<br /> <a href="http://bioinformatics.oxfordjournals.org/content/28/1/84.full">Multifunctional proteins revealed by overlapping clustering in protein interaction network</a></li>
</ul>
</li>
</ul>
<p>スパースなグラフに対する Non-overlapping で最も高速なのは、貪欲アルゴリズムを用いた手法 (O(n (log n)^2))。<br /> 今回はこちらを採用。</p>
<p> </p>
<h4>実装</h4>
<p>R であれば <a href="http://igraph.org/">igraph – Network analysis software</a> に既に実装がある。</p>
<p>Python の NetworkX では過去に議論された形跡が見られるものの、取り込まれる見込みは薄そうだ。</p>
<ul>
<li><a href="https://github.com/networkx/networkx/issues/245">Fast Algorithm for Finding Community Structure (migrated from Trac #245) · Issue #245 · networkx/networkx</a></li>
</ul>
<p>GraphX には、もちろん無い。</p>
<p>今回はとにかく消費メモリを節約したかったので、論文作者本人が公開している C++ の実装を使うことにする。</p>
<ul>
<li><a href="http://cs.unm.edu/%7Eaaron/research/fastmodularity.htm">Structure & Strangeness</a></li>
</ul>
<p> </p>
<h4>ビルド</h4>
<p>上記の URL で公開されているソースコードをビルドする。(ライセンスが GPL なので注意)</p>
<p>コンパイルオプションなどが今日のコンパイラに追随していなかったので、若干の修正を要した。</p>
<ul>
<li>Makefile (-fforce-mem オプション削除)</li>
</ul>
<pre class="brush: plain; gutter: false; highlight:[6]">@@ -8,7 +8,7 @@
# compiler name and flags
CCC = g++
-CCFLAGS = -O3 -fomit-frame-pointer -funroll-loops -fforce-mem -fforce-addr -fexpensive-optimizations
+CCFLAGS = -O3 -fomit-frame-pointer -funroll-loops -fforce-addr -fexpensive-optimizations
# loader flags
LDFLAGS =
</pre>
<ul>
<li>fastcommunity_mh.cc (iostream.h -> iostream, string.h 追加)</li>
</ul>
<pre class="brush: plain; gutter: false; highlight:[6,12]">@@ -71,12 +71,13 @@
//
////////////////////////////////////////////////////////////////////////
-#include <iostream.h>
+#include <iostream>
#include <fstream>
#include <string>
#include "stdlib.h"
#include "time.h"
#include "math.h"
+#include "string.h"
#include "maxheap.h"
#include "vektor.h"</pre>
<ul>
<li>maxheap.h (iostream.h -> iostream, namespace std 追加)</li>
</ul>
<pre class="brush: plain; gutter: false; highlight:[6,7]">@@ -40,7 +40,8 @@
#if !defined(MAXHEAP_INCLUDED)
#define MAXHEAP_INCLUDED
-#include <iostream.h>
+#include <iostream>
+using namespace std;
/* Because using this heap requires us to be able to modify an arbitrary element's
data in constant O(1) time, I use to tricky tactic of having elements in an array-</pre>
<ul>
<li>vektor.h (iostream.h -> iostream)</li>
</ul>
<pre class="brush: plain; gutter: false; highlight:[6]">@@ -30,7 +30,7 @@
#if !defined(vektor_INCLUDED)
#define vektor_INCLUDED
-#include <iostream.h>
+#include <iostream>
#if !defined(vektor_INCLUDED)
#define vektor_INCLUDED</pre>
<p>その後、ディレクトリ内で make コマンドを行えば、実行ファイル FastCommunityMH が生成される。</p>
<p> </p>
<h4>実行</h4>
<p>まずは有名(?)な空手クラブの派閥争いのデータで動作確認してみる。</p>
<pre class="brush: plain; gutter: false;">
$ curl -O http://tuvalu.santafe.edu/~aaronc/hierarchy/karate.pairs
$ ./FastCommunity_GPL_v1.0.3/FastCommunityMH -f ./karate.pairs -l firstRun
</pre>
<p>以下の 2ファイルが成果物としてカレントディレクトリに出力される。</p>
<pre class="brush: plain; gutter: false;" title="karate-fc_firstRun.info">FASTCOMMUNITY_INFERENCE_ALGORITHM
START-----: Sun Nov 22 01:54:13 2015
---FILES--------
D_IN------: ./
D_OUT-----: ./
F_IN------: karate.pairs
F_JOINS---: karate-fc_firstRun.joins
F_INFO----: karate-fc_firstRun.info
---NET_STATS----
MAXID-----: 34
NUMNODES--: 34
NUMEDGES--: 78
---MODULARITY---
MAXQ------: 0.383136
STEP------: 32
EXIT------: Sun Nov 22 01:54:13 2015</pre>
<pre class="brush: plain; gutter: false;" title="karate-fc_firstRun.joins">-1 -1 -0.0498028 0
17 6 -0.0376397 1
6 7 -0.0139711 2
7 1 -0.00147929 3
5 1 0.0177515 4
11 1 0.0490631 5
27 30 0.0612262 6
30 34 0.0784845 7
24 34 0.0946746 8
28 34 0.111111 9
26 25 0.123192 10
25 32 0.145874 11
13 4 0.157709 12
22 2 0.16905 13
31 9 0.180227 14
9 33 0.196992 15
10 3 0.208169 16
18 2 0.219181 17
12 1 0.229372 18
8 4 0.239563 19
4 3 0.253369 20
14 3 0.269149 21
2 3 0.289448 22
29 32 0.29931 23
32 34 0.311144 24
23 33 0.320513 25
19 33 0.329553 26
21 33 0.338264 27
33 34 0.349359 28
16 34 0.362837 29
15 34 0.375986 30
1 20 0.380671 31
20 3 0.383136 32</pre>
<p>この *.joins ファイルを追っていけば、Q値の推移、検出されたクラスタの状態を把握することができる。</p>
<p>-c オプション(特定ステップ時点の状態をファイルに出力)や -v --v オプション(詳細表示)も約に立つが、その度に全データの再計算が行われてしまうので、ビッググラフでは扱いづらい。</p>
<p>ノード数が増えたら、*.joins ファイルを解析するためのツールを作る必要があるだろう。</p>
<p> </p>
<h4>ビッググラフへの適用</h4>
<p>300万ノード、1300万エッジのグラフに対して、上記のプログラムを実行してみた。</p>
<p>期待どおりメモリ使用量は 10GB 以内とかなり少なく抑えられている。</p>
<p>しかし、並列処理には向いていないアルゴリズムなので、それなりの実行時間はかかりそう。<br /> おそらく、完了までは10日程度を要するであろう。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-12368835874611331472015-11-11T08:55:00.001+09:002015-11-11T08:55:06.033+09:00Python: How to Avoid 'multiprocessing' TypeError on Exit<p>Python: multiprocessing TypeError の回避方法</p>
<p> </p>
<p>Python 2.6 でテスト (python setup.py test) をしたとき、最後に以下のようなエラーが出ることがある。</p>
<pre class="brush: plain; gutter: false;">Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "/opt/python/2.6.9/lib/python2.6/atexit.py", line 24, in _run_exitfuncs
func(*targs, **kargs)
File "/opt/python/2.6.9/lib/python2.6/multiprocessing/util.py", line 258, in _exit_function
info('process shutting down')
TypeError: 'NoneType' object is not callable</pre>
<p>回避策は、以下に従い setup.py に記述をすること。</p>
<ul>
<li><a href="http://bugs.python.org/issue15881#msg170215">Issue 15881: multiprocessing 'NoneType' object is not callable - Python tracker</a></li>
</ul>
<pre class="brush: python; gutter: false;">try:
# Work around a traceback on Python < 2.7.4 and < 3.3.1
# http://bugs.python.org/issue15881#msg170215
import multiprocessing # noqa: unused
except ImportError:
pass</pre>
<p>コメントに noqa を付けると、pep8 などのチェックを回避できる。</p>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-55557205872398537492015-11-08T18:59:00.001+09:002015-11-08T19:01:13.504+09:00Universal Use of 'subprocess' Module in Python<p>Python: バージョン・OS 透過的な subprocess モジュールの利用</p>
<p> </p>
<p>Python のバージョン (2.6 / 2.7 / 3.2 / 3.3 / 3.4 / 3.5) や、OS環境 (Linux / Mac / Windows /CygWin) に依存せずに外部コマンドを実行するコードを書きたい。</p>
<p> </p>
<h4>ワークアラウンド</h4>
<p>なかなか一筋縄ではいかない。</p>
<h5>Python3.2 + POSIX環境 では、コマンドラインを bytes で渡してはいけない</h5>
<p>以下のようなエラーが出る。</p>
<pre class="brush: python; gutter: false;">>>> import subprocess
>>> subprocess.call(b'echo')
TypeError: expect bytes or str, not int
</pre>
<p>subprocess モジュールの中で、args が文字列かどうかを判定するときに bytes である場合の考慮が不足しており、bytes の個々の要素(int)に対して分割が行われてしまうため。</p>
<ul>
<li><a href="http://bugs.python.org/issue8513">Issue 8513: subprocess: support bytes program name (POSIX) - Python tracker</a></li>
</ul>
<p>対策は、コマンドラインをリストとして渡せばよい。以下のように対策する。(args が bytes である場合)</p>
<pre class="brush: python; gutter: false;">import sys
if sys.version_info[:2] == (3, 2):
args = [args]</pre>
<p> </p>
<h5>Python3 + Windows環境では、コマンドラインと環境変数のキー・値を bytes で渡してはいけない</h5>
<p>以下のようなエラーが出る。</p>
<pre class="brush: python; gutter: false;">>>> import subprocess
>>> subprocess.call(b'cmd /C echo x')
needquote = (" " in arg) or ("\t" in arg) or not arg
TypeError: argument of type 'int' is not iterable</pre>
<p>bytes に対する in オペレーションに失敗しているが、これといった対策が見当たらない。bytes で渡すのをやめて、str で渡すことにする。(その場合、コマンドライン文字列のエンコードは指定できない)</p>
<pre class="brush: python; gutter: false;">import sys
import six
if six.PY2 or sys.platform != 'win32':
# この場合だけ bytes にエンコード
</pre>
<p>env の指定についても同様。キーだけではなく、値も bytes が許容されていないので注意。</p>
<p> </p>
<h5>POSIX環境 + shell=True のとき、args をリストとして渡してはいけない</h5>
<p>リストの2番目以降の引数が評価されない。</p>
<pre class="brush: python; gutter: false;">>>> import subprocess
>>> subprocess.call(['echo', '3'], shell=True)
0</pre>
<p>ワークアラウンドとしては、subprocess.list2cmdline を使って適切な文字列を生成する。</p>
<pre class="brush: plain; gutter: false;">import sys
import subprocess
if shell and sys.platform != 'win32':
args = [subprocess.list2cmdline(args)]</pre>
<p> </p>
<h4>外部コマンドについて</h4>
<p> </p>
<h5>sleep</h5>
<ul>
<li><a href="http://stackoverflow.com/questions/1672338/how-to-sleep-for-5-seconds-in-windowss-command-prompt-or-dos">How to sleep for 5 seconds in Windows's Command Prompt? (or DOS) - Stack Overflow</a></li>
</ul>
<p>Windows 7 以降は「timeout」というコマンドが標準装備されているが、Python から実行すると標準入力が奪われてしまい不都合だった。
</p>
<p>結局、Python の time モジュールを使うのが一番簡便だという結論に。</p>
<pre class="brush: python; gutter: false;">import subprocess
subprocess.call('python -c "import time;time.sleep(2)"')
</pre>
<p> </p>
<h5>プロセステーブルの取得</h5>
<ul>
<li><a href="https://pypi.python.org/pypi/psutil">psutil 3.2.2 : Python Package Index</a> を使うか</li>
<li>自分で実装するか<ul>
<li>Windows: TASKLIST /FO CSV /NH /FI PID eq プロセスID</li>
<li>POSIX: os.kill(プロセスID, 0)</li>
</ul></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-59018193290061133702015-11-04T00:07:00.001+09:002015-11-04T00:07:40.253+09:00Python: Terminal I/O Assessment in CygWin<p>Python: CygWin 環境におけるターミナル I/O 周りの調査</p>
<h4>環境</h4>
<ul>
<li>OS: Windows Server 2012 R2 日本語版</li>
<li>CygWin: 2.873 (64bit)</li>
<li>Python: 2.7</li>
</ul>
<p> </p>
<h4>目的</h4>
<p>Python 上で以下の操作が可能か調べる。</p>
<ul>
<li>ターミナルのエンコーディングの自動判別</li>
<li>ターミナル画面のクリア</li>
<li>ターミナル上のキー入力の検知</li>
</ul>
<p> </p>
<h4>バリエーション</h4>
<p>調査したバリエーションは以下 5 種類。</p>
<table border="1" bordercolor="grey">
<tbody>
<tr><th>環境</th><th>フロントエンド</th><th>シェル</th><th>Pythonビルド</th><th>エンコーディング</th></tr>
<tr>
<td>1</td>
<td>cmd.exe</td>
<td>-</td>
<td>Windows</td>
<td>cp932</td>
</tr>
<tr>
<td>2</td>
<td>cmd.exe</td>
<td>CygWin bash</td>
<td>Windows</td>
<td>cp932</td>
</tr>
<tr>
<td>3</td>
<td>cmd.exe</td>
<td>CygWin bash</td>
<td>CygWin</td>
<td>UTF-8</td>
</tr>
<tr>
<td>4</td>
<td>MinTTY</td>
<td>CygWin bash</td>
<td>Windows</td>
<td>UTF-8</td>
</tr>
<tr>
<td>5</td>
<td>MinTTY</td>
<td>CygWin bash</td>
<td>CygWin</td>
<td>UTF-8</td>
</tr>
</tbody>
</table>
<p> </p>
<h4>環境情報</h4>
<p>いくつかの方法で情報を取得。(要 import platform,os,sys,locale)</p>
<table border="1" bordercolor="grey">
<tbody>
<tr><th>#</th><th>メソッド</th><th>環境1</th><th>環境2</th><th>環境3</th><th>環境4</th><th>環境5</th></tr>
<tr>
<td>a</td>
<td>platform.system()</td>
<td>Windows</td>
<td>Windows</td>
<td>CYGWIN_NT-6.3</td>
<td>Windows</td>
<td>CYGWIN_NT-6.3</td>
</tr>
<tr>
<td>b</td>
<td>os.name</td>
<td>nt</td>
<td>nt</td>
<td>posix</td>
<td>nt</td>
<td>posix</td>
</tr>
<tr>
<td>c</td>
<td>os.environ.get('TERM')</td>
<td>None</td>
<td>cygwin</td>
<td>cygwin</td>
<td>xterm</td>
<td>xterm</td>
</tr>
<tr>
<td>d</td>
<td>os.environ.get('LANG')</td>
<td>None</td>
<td>ja_JP.UTF-8</td>
<td>ja_JP.UTF-8</td>
<td>ja_JP.UTF-8</td>
<td>ja_JP.UTF-8</td>
</tr>
<tr>
<td>e</td>
<td>sys.stdin.isatty()</td>
<td>True</td>
<td>True</td>
<td>True</td>
<td>False</td>
<td>True</td>
</tr>
<tr>
<td>f</td>
<td>sys.stdin.encoding</td>
<td>cp932</td>
<td>cp932</td>
<td>UTF-8</td>
<td>None</td>
<td>UTF-8</td>
</tr>
<tr>
<td>g</td>
<td>sys.stdout.isatty()</td>
<td>True</td>
<td>True</td>
<td>True</td>
<td>False</td>
<td>True</td>
</tr>
<tr>
<td>h</td>
<td>sys.stdout.encoding</td>
<td>cp932</td>
<td>cp932</td>
<td>UTF-8</td>
<td>None</td>
<td>UTF-8</td>
</tr>
<tr>
<td>i</td>
<td>locale.getpreferredencoding()</td>
<td>cp932</td>
<td>cp932</td>
<td>UTF-8</td>
<td>cp932</td>
<td>UTF-8</td>
</tr>
</tbody>
</table>
<p>明快な判定方法は無いが、sys.stdout.encoding -> 環境変数LANG -> locale.getpreferredencoding() の順に信頼すれば良さそうに思える。</p>
<p> </p>
<h4>画面クリア</h4>
<p>以下のメソッドの挙動を確認。(要 import subprocess)</p>
<table border="1" bordercolor="grey">
<tbody>
<tr><th>#</th><th>メソッド</th><th>環境1</th><th>環境2</th><th>環境3</th><th>環境4</th><th>環境5</th></tr>
<tr>
<td>a</td>
<td>subprocess.call('cls', shell=True)</td>
<td>OK</td>
<td>OK</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>b</td>
<td>subprocess.call('cmd /c cls', shell=True)</td>
<td>-</td>
<td>-</td>
<td>OK</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>c</td>
<td>subprocess.call('clear', shell=False)</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>d</td>
<td>subprocess.call(['echo', '-en', r'\ec'], shell=False)</td>
<td>-</td>
<td>-</td>
<td>OK</td>
<td>-</td>
<td>OK</td>
</tr>
<tr>
<td>e</td>
<td>subprocess.call(r'echo -en "\ec"', shell=False)</td>
<td>-</td>
<td>OK</td>
<td>-</td>
<td>OK</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>なかなか判定条件が悩ましい。</p>
<p> </p>
<h4>キー入力の取得</h4>
<p>msvcrt による取得が可能かどうかと、tty、curses が使えるかどうか調査。</p>
<table border="1" bordercolor="grey">
<tbody>
<tr><th>#</th><th>メソッド</th><th>環境1</th><th>環境2</th><th>環境3</th><th>環境4</th><th>環境5</th></tr>
<tr>
<td>a</td>
<td>msvcrt.getch()</td>
<td>OK</td>
<td>OK</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>b</td>
<td>tty.setraw(0)</td>
<td>-</td>
<td>-</td>
<td>OK</td>
<td>-</td>
<td>OK</td>
</tr>
<td>b</td>
<td>curses.screen#getch()</td>
<td>-</td>
<td>-</td>
<td>OK</td>
<td>-</td>
<td>OK</td>
</tr>
</tbody>
</table>
<p>環境4 に限っては、1文字単位でキー入力を取得する手段が無いという結論となった。</p>
<p> </p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-14934018034236969342015-11-03T01:48:00.001+09:002015-11-03T22:29:20.208+09:00How to Clear Terminal Screen in Various Systems<p>ターミナルの画面を消去する方法</p>
<p> </p>
<p>特別なインストールなしで実現したい。</p>
<ul>
<li>POSIX互換環境(Linux/Unix/Mac): clear</li>
<li>Windows
<ul>
<li>コマンドプロンプト (cmd.exe): cls</li>
<li>CygWin (2011以前): cmd /c cls</li>
<li>CygWin (mintty): echo -en "\ec"</li>
</ul>
</li>
</ul>
<p>CygWin の闇は深い。</p>
<p> </p>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="http://cygwin.com/ml/cygwin/2009-03/msg00495.html">Andy Koppe - Re: clearing the scrollback buffer in mintty</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-81880925981091673312015-11-02T01:46:00.001+09:002015-11-02T01:55:10.305+09:00calendar-cli - Command-line Interface for Google Calendar<p>calendar-cli - Googleカレンダーを操作するためのコマンドライン・インターフェース</p>
<p> </p>
<p>Google カレンダーをコマンドラインで操作するニーズがあったので、元々あったスクリプトを CLI として作り変えた。</p>
<p>
</p>
<p>これを使えば、コマンドラインで</p>
<ul>
<li>1日単位のサマリーの表示</li>
<li>予定の登録</li>
</ul>
<p>が、できる。</p>
<p> </p>
<h4>コード</h4>
<ul>
<li><a href="https://github.com/mogproject/calendar-cli">mogproject/calendar-cli</a></li>
</ul>
<p> </p>
<h4>セットアップ</h4>
<p>Google の API を使う都合上、導入までに少し手間がかかる。</p>
<h5>1. API プロジェクトの作成とclient_secret.json の入手</h5>
<ul>
<li><a href="https://console.developers.google.com/project">Google Developers Console</a> を開く (要Googleアカウントのログイン)</li>
<li>新規プロジェクトを作成</li>
<li>Calendar API を有効化
<ul>
<li>API と認証 -> API -> Google Apps API -> Calendar API -> API を有効にする</li>
</ul>
</li>
<li>OAuth 2.0 認証のクライアントを作成 (デバイス種別は Other)
<ul>
<li>API と認証 -> 認証情報 -> OAuth 同意画面 -> サービス名 (calendar-cli でよい) を入力し保存</li>
<li>API と認証 -> 認証情報 -> 認証情報を追加 -> OAuth 2.0 クライアント ID<br /> -> アプリケーションの種類: その他として保存</li>
</ul>
</li>
<li>画面から client_secret.json をダウンロードし保存
<ul>
<li>実際のファイル名はもっと長い可能性あり</li>
</ul>
</li>
</ul>
<p><a title="認証情報 calendar cli" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmhAp_wtodYN9-rc7XmQpUKk81e80C5zO7371DbMVZeqoxmfgsKyOIHbdWpX6Keia_wyl8zUE7oBbqfPgB5r7XlOU0USXhTtyMo41GWY8oAWlsHHQg-DXHqyqY0p0EHP2ii7sJmjwj_MAI/?imgmax=800"><img title="認証情報_-_calendar-cli.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmhAp_wtodYN9-rc7XmQpUKk81e80C5zO7371DbMVZeqoxmfgsKyOIHbdWpX6Keia_wyl8zUE7oBbqfPgB5r7XlOU0USXhTtyMo41GWY8oAWlsHHQg-DXHqyqY0p0EHP2ii7sJmjwj_MAI/?imgmax=800" alt="認証情報 calendar cli" width="400" height="auto" /></a></p>
<p> </p>
<h5>2. calendar-cli のインストール</h5>
<p>pip で導入可能。環境によっては要sudo。</p>
<pre class="brush: plain; gutter: false;highlight:[1,2,4]">$ pip install calendar-cli
$ calendar-cli --version
calendar-cli 0.1.3
$ calendar-cli -h
Usage:
calendar-cli [options]
Print a summary of events on the calendar.
calendar-cli setup [--read-only --credential ]
Generate a credentials file from the client secret.
You need a web browser to proceed.
calendar-cli create [--date <YYYYMMDD> --start <HHMM> --end <HHMM>
--credential <credential_path>] <summary>
Create an event onto the calendar.
Options:
--version show program's version number and exit
-h, --help show this help message and exit
--calendar=CALENDAR set calendar id to CALENDAR (default:primary)
--date=YYYYMMDD set date to YYYYMMDD in the setup/create command
(default:today)
--credential=CREDENTIAL
set credential path to CREDENTIAL
(default:/Users/user/.credentials/calendar-cli.json)
--read-only create a read-only credential file in the setup
command (default: False)
--start=HHMM set start time in the create command
--end=HHMM set end time in the create command
--debug enable debug logging (default: False)</pre>
<p> </p>
<h5>3. 認証用ファイルの作成</h5>
<p>calendar-cli でセットアップ用のコマンドを用意した。<br /> client_secret.json の部分は、手順1でダウンロードした適切なパスに書き換えること。</p>
<pre class="brush: plain; gutter: false;">$ calendar-cli setup client_secret.json</pre>
<p>一度ブラウザ上で OAuth 認証が行われた後、~/.credentials/calendar-cli.json として認証情報が保存される。</p>
<p> </p>
<h4>使い方</h4>
<p> </p>
<h5>1. サマリーの表示</h5>
<ul>
<li>今日の予定を表示 (デフォルトのカレンダー)<br />
<pre class="brush: plain; gutter: false;">$ calendar-cli</pre>
</li>
<li>指定した日の予定を表示 (デフォルトのカレンダー)<br />
<pre class="brush: plain; gutter: false;">$ calendar-cli --date 12/6</pre>
<br />日付として指定可能なフォーマットは以下。年を省略すると現在の年で補完される。
<ul>
<li>20151206 (YYYYmmdd)</li>
<li>2015-12-6, 2015-12-06 (YYYY-m-d)</li>
<li>2015/12/6, 2015/12/06 (YYYY/m/d)</li>
<li>12-6-2015, 12-06-2015 (m-d-YYYY)</li>
<li>12/6/2015, 12/06/2015 (m/d/YYYY)</li>
<li>12-6, 12-06 (m-d)</li>
<li>12/6, 12/06 (m/d)</li>
</ul>
</li>
<li>指定したカレンダーの今日の予定を表示<br />
<pre class="brush: plain; gutter: false;highlight:1">$ calendar-cli --calendar xxxxxx@group.calendar.google.com
[終日] 有給休暇 (James LaBrie)
[11:00-12:00] ○○様来社 (John Petrucci)
[14:00-15:00] [外出] △△訪問 (John Myung)
[17:30-22:00] □□勉強会 (Jordan Rudess)
[17:30-23:00] ☆☆飲み会 (Mike Mangini)</pre>
</li>
</ul>
<p> </p>
<h5>2. イベントの登録</h5>
<ul>
<li>当日または翌日の 10:30 に 15分のイベントを作成<br />
<pre class="brush: plain; gutter: false;highlight:1">$ calendar-cli create --start 10:30 内容
イベントを作成しました: 2015-11-02 月 [10:30-10:45] 内容</pre>
現在時刻が 10:30 以前であれば当日、以降であれば翌日にイベントが作成される。<br />時刻指定のフォーマットは以下。
<ul>
<li>1030 (HHMM)</li>
<li>9:30, 09:30 (H:M)</li>
</ul>
</li>
<li>当日または翌日に 12:00-13:00 のイベントを作成<br />
<pre class="brush: plain; gutter: false;highlight:1">$ calendar-cli create --start 12:00 --end 13:00 内容
イベントを作成しました: 2015-11-02 月 [12:00-13:00] 内容</pre>
現在時刻が 12:00 以前であれば当日、以降であれば翌日にイベントが作成される。</li>
<li>日付を指定してイベントを作成<br />
<pre class="brush: plain; gutter: false;highlight:1">$ calendar-cli create --date 12/6 --start 12:00 --end 13:00 内容
イベントを作成しました: 2015-12-06 日 [12:00-13:00] 内容</pre>
--date オプションのフォーマットはサマリー表示と同じ。</li>
<li>終日イベントを作成<br />
<pre class="brush: plain; gutter: false;highlight:1">$ calendar-cli create --date 12/12 内容
イベントを作成しました: 2015-12-12 土 [終日] 内容</pre>
</li>
</ul>
<p> </p>
<h5>Related Posts</h5>
<ul>
<li><a href="http://mogproject.blogspot.jp/2013/07/reading-event-data-on-google-calendar.html">mog project: Reading Event Data on Google Calendar in Python</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-72672085831734199832015-11-01T15:17:00.001+09:002015-11-01T22:52:27.577+09:00Type Assertion Decorator in Python<p>Python: 型チェックのデコレータ</p>
<p> </p>
<p>Python は動的型付けの言語だが、抽象的なライブラリを書こうとすればどうしても型チェックの需要が生じる。</p>
<ul>
<li><a href="https://andreacensi.github.io/contracts/index.html">PyContracts</a> は非常に良さそうなライブラリだが、独自のクラスには対応していない模様。</li>
<li>なので、車輪の再発明と知りつつも自前で実装してみた。</li>
</ul>
<p> </p>
<h4>コード</h4>
<ul>
<li><a href="https://github.com/mogproject/mog-commons-python/blob/master/src/mog_commons/types.py#L123-L133">mog-commons-python/types.py at master · mogproject/mog-commons-python</a></li>
</ul>
<p> </p>
<h4>インストール</h4>
<p>mog-commons-python の一部として実験的にリリースしている。</p>
<pre class="brush: plain; gutter: false;">pip install mog-commons</pre>
<p>inspect#getcallargs をバックポートしたので、Python 2.6 / 2.7 / 3.2 / 3.3 / 3.4 / 3.5 の全てで動く。</p>
<p> </p>
<h4>使い方</h4>
<p>関数定義と一緒に @types デコレータを指定するだけ。</p>
<ul>
<li>1個以下の可変長引数として、戻り値の型を指定 (省略も可能)</li>
<li>名前付き引数として、パラメータ名=型 と制約を定義</li>
<li>ListOf, DictOf などを使えば、コレクションの要素の型もチェックできる</li>
<li>チェックに失敗したら、TypeError が発生</li>
</ul>
<pre class="brush: python; gutter: false;">>>> from mog_commons.types import *
>>> @types(float, x=int, y=float, z=ListOf(int))
... def f(x, y, z):
... return x * y + sum(z)
...
>>> f(1, 2, 3)
TypeError: y must be float, not int.
>>> f(1, 2.0, 3)
TypeError: z must be list(int), not int.
>>> f(1, 2.0, [3, 4])
9.0
</pre>
<p>戻り値だけのチェックも可能。</p>
<pre class="brush: python; gutter: false;">>>> @types(bool)
... def g(x):
... return x
...
>>> g(3)
TypeError: must return bool, not int.
>>> g(True)
True</pre>
<p>簡便のため、Python バージョンに透過的な String, Unicode, Option も定義した。<br /> 可変長引数、名前付き引数をチェックする場合は以下のようにする。</p>
<pre class="brush: python; gutter: false;">>>> @types(args=VarArg(String), kwargs=KwArg(int))
... def h(*args, **kwargs):
... return 'ok'
...
>>> h('abc', 5, a=123, b=456)
TypeError: args must be tuple((basestring|str)), not tuple.
>>> h('abc', 'def', a=123, b=456)
'ok'
</pre>
<p>実際にどの要素の型が異なっているのか、までは表示していない。</p>
<p> </p>
<p>これで、Typesafe Python にまた一歩近づいた。</p>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-35384787850573642322015-10-24T19:53:00.001+09:002015-10-24T20:02:02.241+09:00Launching Windows Server in Amazon Web Services<p>AWS で Windows サーバ (EC2) を起動する</p>
<p> </p>
<h4>インスタンスの作成</h4>
<ul>
<li><a href="https://console.aws.amazon.com/">AWSマネジメントコンソール</a> にサインイン</li>
<li>サービス -> EC2</li>
<li>画面右上のメニューからリージョンを選択: 今回は節約のため「米国東部 (バージニア北部)」</li>
<li>EC2 ダッシュボード -> インスタンスの作成</li>
</ul>
<p> </p>
<h5>ステップ 1: Amazon マシンイメージ (AMI)</h5>
<ul>
<li>クイックスタート -> Microsoft Windows Server 2012 R2 Base (64ビット) を選択
<ul>
<li>日本語版を使用したい場合は、コミュニティ AMI -> 「2012-R2 Japanese Base」などで検索して見つかったものを選択</li>
</ul></li>
</ul>
<h5>ステップ 2: インスタンスタイプの選択</h5>
<ul>
<li>t2.micro を選択し、「次の手順: インスタンスの詳細の設定」をクリック</li>
</ul>
<h5>ステップ 3: インスタンスの詳細の設定</h5>
<ul>
<li>デフォルトのまま、「次の手順: ストレージの追加」をクリック</li>
</ul>
<h5>ステップ 4: ストレージの追加</h5>
<ul>
<li>ルートディスクが 30GB 以上でないと作成に失敗する。</li>
<li>「次の手順: インスタンスのタグ付け」をクリック</li>
</ul>
<h5>ステップ 5: インスタンスのタグ付け</h5>
<ul>
<li>キー: Name</li>
<li>値: win2012-1 (適宜設定)</li>
<li>「次の手順: セキュリティグループの設定」をクリック</li>
</ul>
<h5>ステップ 6: セキュリティグループの設定</h5>
<ul>
<li>セキュリティグループの割り当て: 新しいセキュリティグループを作成する</li>
<li>セキュリティグループ名: security-win</li>
<li>説明: Security group for Windows servers</li>
<li>タイプ: RDP (プロトコル: TCP, ポート範囲: 3389)</li>
<li>送信元: マイIP</li>
<li>「確認と作成」をクリック</li>
</ul>
<h5>ステップ 7: インスタンス作成の確認</h5>
<ul>
<li>「作成」をクリック</li>
<li>既存のキーペアを選択するか、新しいキーペアを作成します。
<ul>
<li>新しいキーペアの作成</li>
<li>キーペア名: aws-win (適宜設定)</li>
<li>「キーペアのダウンロード」をクリック</li>
<li>aws-win.pem をダウンロードし保存
<pre class="brush: plain; gutter: false;">$ mv -i ~/Downloads/aws-win.pem ~/.ssh/
$ chmod 600 ~/.ssh/aws-win.pem</pre>
</li>
</ul>
</li>
<li>「インスタンスの作成」をクリック</li>
<li>作成処理に成功したら、「インスタンスの表示」をクリック</li>
</ul>
<p> </p>
<h4>パスワードの入手</h4>
<ul>
<li>
<p>起動してから数分後、インスタンス一覧の画面で Windows サーバを右クリックし<br />
「Windows パスワードの取得」を選択</p>
</li>
<li>キーペアのパス: aws-win.pem の保存先を選択</li>
<li>「パスワードの暗号化」をクリック</li>
<li>画面に以下の情報が表示される
<ul>
<li>パブリック IP</li>
<li>ユーザー名</li>
<li>パスワード</li>
</ul>
</li>
</ul>
<p> </p>
<h4>リモートデスクトップ接続</h4>
<ul>
<li>Mac から Windows に接続する場合、まず App Store から Microsoft Remote Desktop をインストールする
<ul>
<li><a href="https://itunes.apple.com/jp/app/microsoft-remote-desktop/id715768417">Microsoft Remote Desktop を Mac App Store で</a></li>
</ul>
</li>
<li>アプリ起動後、New で新規接続情報を入力</li>
<ul>
<li>Connection name: win2012-1</li>
<li>PC name: (先程入手したパブリックIP)</li>
<li>User name: Administrator</li>
<li>Password: (先程入手したパスワード)</li>
<li>Start session in full screen: チェックを外す (お好みで)</li>
<li>Use all monitors: チェックを外す<br /> (デュアルディスプレイの場合に、Windows側 も 2画面になってしまうのを防ぐ)</li>
</ul>
<li>接続時、Verify Certificate のウィンドウが出たら、<br /> Show Certificate -> Always trust ... にチェックを付けてから Continue をクリック</li>
<li>フルスクリーンの切り替えは Command+1 でできる</li>
</ul>
<p><a title="Fullscreen 10 24 15 7 09 PM" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9Av5soc2yU0jjkpzdOOpbdegwRvXVJYo8G25UJvjQNuqI3LOcstJVTurn8Nz0vH2C13WjBqJHIKzUqxgwY_E-qpqheKg0GGUxRdpTQO4l46xXiaU6aLIQq70D5iCulWPKRA8KH0kMx5o8/?imgmax=800"><img title="Fullscreen_10_24_15__7_09_PM.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9Av5soc2yU0jjkpzdOOpbdegwRvXVJYo8G25UJvjQNuqI3LOcstJVTurn8Nz0vH2C13WjBqJHIKzUqxgwY_E-qpqheKg0GGUxRdpTQO4l46xXiaU6aLIQq70D5iCulWPKRA8KH0kMx5o8/?imgmax=800" alt="Fullscreen 10 24 15 7 09 PM" width="400" height="auto" /></a></p>
<p> </p>
<h4>Windows 上で Python をセットアップしてみる</h4>
<ul>
<li>Internet Explorer を起動し、Python 公式サイトからインストーラーをダウンロードし実行。
<ul>
<li><a href="https://www.python.org/downloads/windows/">https://www.python.org/downloads/windows/</a></li>
</ul>
</li>
<li>PowerShell 上でパスを通す<br />
<pre class="brush: plain; gutter: false;">setx PATH $Env:PATH";C:\Python27;C:\Python27\Scripts"</pre>
</li>
<li>その後、PowerShell の落とし上げをすれば、python や pip が使えるようになる。</li></ul>
<p> </p>
<p>数百円で、すぐに使える Windows 環境が手に入るのは本当に素晴らしい。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-31520106819708797792015-10-21T01:48:00.001+09:002015-10-21T01:48:14.301+09:00Running Windows-OS CI for Python Projects on AppVeyor<p>AppVeyor を使って Windows 環境で Python プロジェクトの CI を回す</p>
<p> </p>
<p><a href="https://github.com/mogproject/easy-menu">easy-menu</a> を AppVeyor の CI に載せたので簡単にメモ。</p>
<ul>
<li><a href="https://ci.appveyor.com/project/mogproject/easy-menu">easy-menu - AppVeyor</a></li>
</ul>
<p> </p>
<h4>AppVeyor とは</h4>
<ul>
<li><a href="http://www.appveyor.com/">Continuous Integration and Deployment service for Windows developers - Appveyor</a></li>
</ul>
<p>Windows OS (デフォルトは Windows Server 2012) 環境で CI を実行してくれるサービス。</p>
<p>OSS は無料で制限なく使えるので、Windows 版 Travis CI のようなものと考えてよい。</p>
<p> </p>
<h4>セットアップ</h4>
<p> </p>
<h5>プロジェクト追加</h5>
<p>GitHub のアカウントで認証し、画面からプロジェクトを追加するだけ。</p>
<h5>appveyor.yml</h5>
<p>以下のリポジトリから、appveyor.yml と appveyor ディレクトリ配下のスクリプトを拝借した。</p>
<ul>
<li><a href="https://github.com/ogrisel/python-appveyor-demo">ogrisel/python-appveyor-demo</a></li>
</ul>
<p>基本的にそのままで動くが、バリエーションが多すぎるので適宜減らすとよい。<br />
(最初、appv「a」yor.yml と typo していたため全く機能せず、焦った)</p>
<p> </p>
<h4>ハマりどころ</h4>
<p> </p>
<h5>mock インストール失敗</h5>
<p>環境の setuptools が古いせいか、mock ライブラリのインストールで失敗した。</p>
<ul>
<li><a href="https://github.com/testing-cabal/mock/issues/261">Requires setuptools >=17.1 to install properly. · Issue #261 · testing-cabal/mock</a></li>
</ul>
<p>ワークアラウンドとして、'mock == 1.0.1' とバージョンを固定したら通るようになった。</p>
<p> </p>
<h5>そもそもテストが通らない</h5>
<p>OS に対して透過的なテストを書くことは、本当に難しい。</p>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-44180517297203178612015-09-19T15:05:00.001+09:002015-09-19T16:23:03.020+09:00Python: How to Implement Thread-Safe Auto-Increment in Redis<p>Python: Redis上でスレッドセーフなオート・インクリメントを実現する</p>
<p> </p>
<h4>目的</h4>
<p>Redisで以下のような3種類のDBを使い、色々な名前の登録処理をしたい。</p>
<ul>
<li>名前 -> ID の検索テーブル</li>
<li>ID -> 名前 の検索テーブル</li>
<li>特定の key で ID の登録数 (=払い出したIDの最大値) を保持するテーブル</li>
</ul>
<p>ID は 1 を起点とするオート・インクリメントなもので、名前ごとにユニークな数値を割り当てる。</p>
<p> </p>
<h4>インターフェース</h4>
<p>1個の Redis インスタンスの中にある 3つの DBを使用する想定。それぞれのDB番号とカウンターとして使用するキーの名前を指定して初期化。</p>
<p>register メソッドは文字列として name を受け取り、その name に対応するユニークな ID を返す。name が DB に登録されていない場合のみ、登録処理を行う。</p>
<pre class="brush: python; gutter: true;">import redis
class Registerer(object):
def __init__(self, counter_key, db_counter, db, db_invert=None,
host='localhost', port=6379):
self.counter_key = counter_key
self.db_counter = db_counter
self.db = db
self.db_invert = db_invert
self.host = host
self.port = port
self.redis = redis.Redis(host, port, db)
self.redis_cnt = redis.Redis(host, port, db_counter)
self.redis_inv = (redis.Redis(host, port, db_invert)
if db_invert is not None else None)
def register(self, name):
return ???</pre>
<p> </p>
<h4>テスト</h4>
<p>マルチスレッド・プログラミングをする時はテストがないと不安なので、先にテストを書く。<br />
(実行すると、ローカルの Redis のデータは全て消える)</p>
<pre class="brush: python; gutter: true;" title="test_registerer.py">#!/usr/bin/env python
import unittest
import threading
from repos.registerer import Registerer
class TestRegisterer(unittest.TestCase):
def _clear(self):
self.r.redis.flushall()
def setUp(self):
self.num_records = 10000
self.r = Registerer('count', 15, 14, 13)
self._clear()
def tearDown(self):
self._clear()
def test_register_serial(self):
r = self.r
for i in range(self.num_records):
r.register('user-%04d' % i)
for i in range(10):
r.register('user-%04d' % i)
self.assertEqual(r.redis.dbsize(), self.num_records)
self.assertEqual(r.redis_inv.dbsize(), self.num_records)
self.assertEqual(int(r.redis_cnt.get('count')), self.num_records)
for i in range(100):
self.assertEqual(int(r.redis.get(r.redis_inv.get(i * 50 + 1))), i * 50 + 1)
self.assertEqual(r.redis_inv.get(i * 50 + 1), 'user-%04d' % (i * 50))
def test_register_parallel(self):
r = self.r
threads = [threading.Thread(target=r.register, args=('user-%04d' % i,)) for i in range(10000)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(r.redis.dbsize(), self.num_records)
self.assertEqual(r.redis_inv.dbsize(), self.num_records)
self.assertEqual(int(r.redis_cnt.get('count')), self.num_records)
for i in range(100):
self.assertEqual(int(r.redis.get(r.redis_inv.get(i * 50 + 1))), i * 50 + 1)
# this will fail
# self.assertEqual(r.invert_redis.get(i * 50 + 1), 'user-%04d' % (i * 50))
def test_register_parallel_same_id(self):
r = self.r
threads = [threading.Thread(target=r.register, args=('user-0000',)) for _ in range(self.num_records)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(r.redis.dbsize(), 1)
self.assertEqual(r.redis_inv.dbsize(), 1)
self.assertEqual(int(r.redis_cnt.get('count')), 1)
self.assertEqual(int(r.redis.get('user-0000')), 1)
self.assertEqual(r.redis_inv.get(1), 'user-0000')
if __name__ == '__main__':
unittest.main()
</pre>
<p> </p>
<h4>ナイーブな実装 (問題あり)</h4>
<p>名前 -> ID のテーブルを検索し、値が取得できなかったら Redis の INCR コマンドで値を更新し、その ID を正引き/逆引きのテーブルそれぞれ登録する。</p>
<p>念の為に SETNX コマンドを利用し、キーが既に存在していたら例外を送出するようにする。</p>
<pre class="brush: python; gutter: true;"> def register_naive(self, name):
index = self.redis.get(name)
if index is None:
index = self.redis_cnt.incr(self.counter_key)
if not self.redis.setnx(name, index):
raise Exception(
'Failed to register: db=%d, key=%s, value=%s' %
(self.db, name, index))
if self.db_invert is not None:
if not self.redis_inv.setnx(index, name):
raise Exception(
'Failed to register: db=%d, key=%s, value=%s' %
(self.db_invert, index, name))
return int(index)</pre>
<p>Redis の INCR はアトミックな処理であるし、一見問題はなさそうだが、先程書いたテストが失敗する。</p>
<pre class="brush: plain; gutter: false;">.Exception in thread Thread-10001:
Traceback (most recent call last):
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/threading.py", line 810, in __bootstrap_inner
self.run()
File "/Users/xxxxxx/.pyenv/versions/2.7.9/lib/python2.7/threading.py", line 763, in run
self.__target(*self.__args, **self.__kwargs)
File "/xxxxxx/registerer.py", line 36, in register_naive
raise Exception('Failed to register: db=%d, key=%s, value=%s' % (self.db, name, index))
Exception: Failed to register: db=14, key=user-0000, value=1
F.
======================================================================
FAIL: test_register_parallel_same_id (__main__.TestRegisterer)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./tests/test_registerer.py", line 72, in test_register_parallel_same_id
self.assertEqual(int(r.redis_cnt.get('count')), 1)
AssertionError: 2 != 1
----------------------------------------------------------------------
Ran 3 tests in 9.492s
FAILED (failures=1)</pre>
<p>これは、同じ名前を同時に大量に登録した場合にのみ発生する。<br />
名前 -> IDテーブルのチェックと更新の間のタイミングで競合状態が発生し、同じ名前の登録処理が複数実行されているためである。</p>
<p> </p>
<h4>トランザクションを利用した実装</h4>
<p>redis-py には transaction というお誂え向きのヘルパーメソッドが用意されている。</p>
<p>パイプラインを引数に取る関数と、楽観的ロックをかけるキーを指定するだけでシンプルにトランザクションを記述できる。</p>
<p>最終的なコードは以下のようになった。</p>
<pre class="brush: python; gutter: true;" title="registerer.py">import redis
class Registerer(object):
def __init__(self, counter_key, db_counter, db, db_invert=None,
host='localhost', port=6379):
self.counter_key = counter_key
self.db_counter = db_counter
self.db = db
self.db_invert = db_invert
self.host = host
self.port = port
self.redis = redis.Redis(host, port, db)
self.redis_cnt = redis.Redis(host, port, db_counter)
self.redis_inv = (redis.Redis(host, port, db_invert)
if db_invert is not None else None)
def register(self, name):
def f(pipe):
index = self.redis.get(name)
if index is None:
index = pipe.incr(self.counter_key)
if not self.redis.setnx(name, index):
raise Exception(
'Failed to register: db=%d, key=%s, value=%s' %
(self.db, name, index))
if self.db_invert is not None:
if not self.redis_inv.setnx(index, name):
raise Exception(
'Failed to register: db=%d, key=%s, value=%s' %
(self.db_invert, index, name))
return int(index)
return self.redis_cnt.transaction(
f, self.counter_key, value_from_callable=True)
</pre>
<p> </p>
<p>無事、テストも通った。</p>
<pre class="brush: plain; gutter: false;">...
----------------------------------------------------------------------
Ran 3 tests in 13.818s
OK</pre>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="https://github.com/andymccurdy/redis-py#pipelines">andymccurdy/redis-py - Pipelines</a></li>
</ul>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-63211548221438402122015-09-18T00:45:00.001+09:002015-09-18T00:45:02.805+09:00Image Conversion Cheat Sheet<p>画像変換チートシート</p>
<p> </p>
<p>コマンドラインで画像変換を完結するためのメモ。</p>
<script src="https://gist.github.com/mogproject/b12ffe9e0ad2f893c113.js"></script>
mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-16446740648401548402015-09-02T23:17:00.001+09:002015-09-02T23:22:22.464+09:00Getting Started with Apache Spark Cluster and GraphX<p>Spark クラスタを 10分 で構築して GraphX を試してみる</p>
<p> </p>
<p>ローカルモードで動かした後の次のステップ</p>
<h4>前提条件</h4>
<ul>
<li>OS: Mac or Linux</li>
<li>JDK: 7+</li>
</ul>
<p> </p>
<h4>インストール</h4>
<ul>
<li><a href="http://spark.apache.org/downloads.html">Downloads | Apache Spark</a>
<ul>
<li>既に構築済みの Hadoop クラスタがあれば、それに応じたパッケージタイプを選択。</li>
<li>「Pre-built for Hadoop 2.6 and later」あたりを選べば無難。</li>
<li>Scala 2.11 を使いたい場合はソースからのビルドが必要。</li>
</ul>
</li>
<li>パッケージを選択した場合、ダウンロードしたファイルを展開すればすぐに使える。</li>
<li>Spark 一式は /usr/local/share/spark として使う。(以降、適当に読み替えること)</li>
<li>log4j.properties ファイルを作成して、不要な INFO ログを抑止する。</li>
</ul>
<p>実行例</p>
<pre class="brush: plain; gutter: false;highlight:[1,6,9]">
# ### ダウンロードと展開、移動
# curl -O http://d3kbcqa49mib13.cloudfront.net/spark-1.4.1-bin-hadoop2.6.tgz
# tar zxf ./spark-1.4.1-bin-hadoop2.6.tgz
# mv -i ./spark-1.4.1-bin-hadoop2.6 /usr/local/share/spark-1.4.1
#
# ### シンボリックリンクを作成
# ln -s /usr/local/share/spark-1.4.1 /usr/local/share/spark
#
# ### ロギング設定
# cd /usr/local/share/spark
# cp -pi ./conf/log4j.properties.template ./conf/log4j.properties
# vi ./conf/log4j.properties
# diff ./conf/log4j.properties.template ./conf/log4j.properties
2c2
< log4j.rootCategory=INFO, console
---
> log4j.rootCategory=WARN, console
</pre>
<p> </p>
<h4>起動</h4>
<p>Hadoop, HDFS が使えなくても Spark の利用は可能。</p>
<p>クラスタの管理サービスにはいくつか選択肢がある。</p>
<ul>
<li>Amazon EC2</li>
<li>Standalone Deploy Mode</li>
<li>Apache Mesos</li>
<li>Hadoop YARN</li>
</ul>
<p>今回は、一番簡単な Standalone Deploy Mode を試す。</p>
<ul>
<li>マスター起動
<pre class="brush: plain; gutter: false;"># /usr/local/share/spark/sbin/start-master.sh</pre>
</li>
<li>スレーブ起動
<pre class="brush: plain; gutter: false;"># /usr/local/share/spark/sbin/start-slave.sh "spark://マスターのIPアドレス:7077"</pre>
マスターのIPアドレスの箇所はホスト名でもよいが、localhost, 127.0.0.1 はデフォルトで接続が拒否されている模様。<br /> マスターとの同居も可。</li>
</ul>
<p> </p>
<h4>動作確認</h4>
<ul>
<li>ログ
<ul>
<li>/usr/local/share/spark/logs 配下に master, worker それぞれのログファイルが作成される。</li>
</ul>
</li>
<li>GUI
<ul>
<li>http://マスターのIPアドレス:8080<br />
<a href="https://lh3.googleusercontent.com/-LMarLBuhaq4/VecF0UfHVdI/AAAAAAAAAoA/Be38RDPcu-4/Spark_Master_at_spark___xxxxxxxx_7077.png?imgmax=800" title="Spark Master at spark xxxxxxxx 7077"><img src="https://lh3.googleusercontent.com/-LMarLBuhaq4/VecF0UfHVdI/AAAAAAAAAoA/Be38RDPcu-4/Spark_Master_at_spark___xxxxxxxx_7077.png?imgmax=800" alt="Spark Master at spark xxxxxxxx 7077" title="Spark_Master_at_spark___xxxxxxxx_7077.png" width="400" height="auto" /></a></li>
</ul>
</li>
</ul>
<p> </p>
<h4>build.sbt</h4>
<p>必要最低限のものだけ。</p>
<pre class="brush: scala; gutter: true;" title="build.sbt">name := "your-app-name"
version := "0.1.0"
scalaVersion := "2.10.4"
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % "1.4.1",
"org.apache.spark" %% "spark-graphx" % "1.4.1"
)
</pre>
<p> </p>
<h4>データ</h4>
<p><a href="https://twitter.com/teppei_tosa">@teppei_tosa</a> さんのチュートリアルに従う。</p>
<ul>
<li><a href="http://teppei.hateblo.jp/entry/2014/12/02/000637">GraphX Advent Calendar - Day 02 - GraphX はじめの一歩 - TEPPEI STUDIO</a></li>
</ul>
<pre class="brush: plain; gutter: false;" title="edge_list.txt">1 2
2 3
3 1
</pre>
<p> </p>
<h4>ソースコード</h4>
<pre class="brush: scala; gutter: true;" title="src/main/scala/Main.scala">import org.apache.spark._
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD
object Main {
def main(args: Array[String]) = {
val conf = new SparkConf().setMaster("spark://MASTER_IP:7077").setAppName("My Application")
val sc = new SparkContext(conf)
val g = GraphLoader.edgeListFile(sc, "path/to/edge_list.txt").cache()
g.vertices.collect().foreach(println(_))
g.edges.collect().foreach(println(_))
sc.stop()
}
}
</pre>
<p> </p>
<h4>実行</h4>
<pre class="brush: plain; gutter: false;">
$ sbt run
(snip)
(3,1)
(1,1)
(2,1)
(snip)
Edge(1,2,1)
Edge(2,3,1)
Edge(3,1,1)
</pre>
<p>INFO ログが大量に出て見にくいが、無事グラフの情報が画面に出力された。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-64732995419956482242015-09-02T05:45:00.001+09:002015-09-02T05:45:24.066+09:00Python3.2: TypeError in subprocess Module When Using Bytes Command String<p>Python3.2: subprocess モジュールでコマンドラインを bytes で書くと TypeError が発生</p>
<p> </p>
<h4>事象</h4>
<p>POSIX互換環境の Python 3.2 で subprocess#call を呼び出すとき<br /> コマンドを bytes で書きつつ shell モードを有効にすると、以下の意味不明なエラーが出る。</p>
<pre class="brush: python; gutter: false;highlight:[6,8]">$ python
Python 3.2.5 (default, Sep 1 2015, 23:07:07)
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.call(b'echo', shell=True)
(snip)
TypeError: Can't convert 'int' object to str implicitly
>>></pre>
<p>コマンドを str で渡せばよいのだが、それだとコマンドライン自体が SJIS などの非UTF-8で表現されている場合に困ってしまう。</p>
<p> </p>
<h4>原因</h4>
<p>子プロセスを生成するところで、args に ['/bin/sh', '-c', 101, 99, 104, 111] というパラメータが渡っていた。<br /> ['/bin/sh', '-c'] + list(b'echo') のようなコードで作られたようだ。</p>
<p>1バイトごとに分解され int となったものが暗黙的に str に変換されずに TypeError が送出される。</p>
<p>Python 3.3 では修正済み。</p>
<ul>
<li><a href="https://github.com/python/cpython/commit/ae1258dbf2532a3ccae0fc8ef477521f0a1ff649">Issue #8513: On UNIX, subprocess supports bytes command string. · python/cpython@ae1258d</a></li>
<li><a href="http://bugs.python.org/issue8513">Issue 8513: subprocess: support bytes program name (POSIX) - Python tracker</a></li>
</ul>
<p> </p>
<h4>対応方法</h4>
<p>Python 3.2 をサポートしないのが賢明と思われるが、以下のようなワークアラウンドでも回避できそうだ。</p>
<pre class="brush: python; gutter: false;">import subprocess
NOT_USE_SHELL = sys.version_info[:2] == (3, 2) and not sys.platform == 'win32'
cmd = b'echo'
if NOT_USE_SHELL:
ret_code = subprocess.call(['/bin/sh', '-c', cmd], shell=False)
else:
ret_code = subprocess.call(cmd, shell=True)
</pre>
<p> </p>
<p>話題になったのが 5年以上前ということもあり、Google検索しても情報が出てこないのがつらい。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-72041026783845431492015-08-31T19:47:00.001+09:002015-08-31T19:47:50.260+09:00SSH Cheat Sheet<p>SSH チートシート</p>
<p> </p>
<p>たまに使うけど忘れがちなコマンド & tips についてメモ。</p>
<script type="text/javascript" src="https://gist.github.com/mogproject/1f1b8b23471d08ca957e.js"></script>
<p> </p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-62002273847216230402015-08-31T03:11:00.001+09:002015-09-02T02:34:53.961+09:00Easy Menu 1.0 is Now Released!<p>新しく生まれ変わった Easy Menu をお試しください</p>
<p> </p>
<h4>Easy Menu とは</h4>
<ul>
<li><a href="https://github.com/mogproject/easy-menu">mogproject/easy-menu</a></li>
</ul>
<p>Easy Menu とは、設定ファイルを元にターミナル上で動作するコマンドランチャーを立ち上げるだけの、とてもシンプルなツールです。</p>
<p>実に 2 年以上の間ずっと放置していたのですが意外と社内で好評でしたので、この際リニューアルしてみました。</p>
<ul>
<li>動作イメージ<br /> <a title="" href="https://raw.githubusercontent.com/wiki/mogproject/easy-menu/img/demo.gif"><img title="" src="https://raw.githubusercontent.com/wiki/mogproject/easy-menu/img/demo.gif" alt="" width="480" height="auto" /></a></li>
</ul>
<p> </p>
<h4>すぐに試す</h4>
<p>今回、パッケージを PyPI に登録することができたので、インストールがとても簡単になりました。</p>
<p>設定ファイルを HTTP(S) 経由で取得すれば、たった 2行のコマンドで Easy Menu をお試しいただけます。</p>
<p><strong><strike>注意: 2015-08-31時点では、まだ Python 3.x系には対応していません</strike><br />
=> 2015-09-02 にリリースした v1.0.2 で Python 3.2/3.3/3.4 に対応しました</strong></p>
<pre class="brush: plain; gutter: false;">pip install easy-menu
easy-menu http://git.io/vGWla</pre>
<ul>
<li>短縮URLをお好みでない方は、こちらのURLをご利用ください。<br /> <a href="https://raw.githubusercontent.com/mogproject/easy-menu/master/easy-menu.example.yml">https://raw.githubusercontent.com/mogproject/easy-menu/master/easy-menu.example.yml</a></li>
<li>ファイルの中身は <a href="https://github.com/mogproject/easy-menu/blob/master/easy-menu.example.yml">こちら</a> です。コマンドは全て echo コマンドなのでご安心ください。</li>
</ul>
<p> </p>
<h5>主な起動方法</h5>
<ul>
<li>easy-menu
<ul>
<li>パラメータを付けずにコマンドを実行した場合、カレントディレクトリから上位に向かって easy-menu.yml が存在するかチェックしていきます。そして最初に見つかった設定ファイルの内容がロードされます。 (Vagrant における Vagrantfile の検索と同じような動きです)</li>
<li>環境変数 EASY_MENU_CONFIG を設定することで、検索対象のファイル名<br /> (デフォルトは easy-menu.yml) を変更することもできます。</li>
<li>プロジェクトのルートディレクトリに easy-menu.yml を置いておくと捗ります。</li>
</ul>
</li>
<li>easy-menu <設定ファイルのパス>
<ul>
<li>もちろん、設定ファイルの場所を直接指定することもできます。<br /> 尚、コマンド実行時には、設定ファイルの存在するディレクトリにカレントディレクトリを移動してからコマンドが実行されます。</li>
</ul>
</li>
<li>easy-menu <設定ファイルのURL>
<ul>
<li>http(s) により設定ファイルをネットワーク上から取得します。</li>
<li>スキームは http, https のみ対応。省略はできません。</li>
</ul>
</li>
</ul>
<p> </p>
<h4>新機能</h4>
<p> </p>
<h5>1行のコマンドでインストール</h5>
<p>前述のとおり、pip コマンドでインストールできるようになりました。</p>
<h5>YAML形式のサポート</h5>
<p>これまでは JSON のみのサポートでしたが、YAML にも対応しました。</p>
<h5>別ファイルのインクルード</h5>
<p>"include" というキーワードを使うことで、別のファイルのメニューをマージすることが可能になりました。</p>
<p>設定イメージ:</p>
<pre class="brush: plain; gutter: false;" title="main.yml">---
メインメニュー:
- include: common.yml
- サブメニュー:
- コマンド3: echo 3
- コマンド4: echo 4</pre>
<pre class="brush: plain; gutter: false;" title="common.yml">---
共通メニュー:
- コマンド1: echo 1
- コマンド2: echo 2</pre>
<p>この場合、以下のようにメニューが生成されます。</p>
<pre class="brush: plain; gutter: false;">---
メインメニュー:
- 共通メニュー:
- コマンド1: echo 1
- コマンド2: echo 2
- サブメニュー:
- コマンド3: echo 3
- コマンド4: echo 4</pre>
<p> </p>
<h5>動的なメニュー生成</h5>
<p>"eval" というキーワードを使うことで、任意のコマンドをメニュー生成時に実行し、その標準出力を YAML 形式の設定として読み込むことが可能になりました。</p>
<p>設定イメージ:</p>
<pre class="brush: plain; gutter: false;">---
メインメニュー:
- eval: scripts/create_ec2_menu.sh
</pre>
<p>この場合、シェルスクリプト create_ec2_menu.sh の出力がそのままメニューの内容として反映されます。</p>
<p>例えば、AWS で起動中の EC2 インスタンスの一覧を動的に取得するような使い方を想定しています。</p>
<h4>国際化対応</h4>
<p>現状、対応しているのは英語と日本語のみです。</p>
<p> </p>
<p>以上、簡単な機能の紹介でした。<br /> 不具合、要望などがありましたら、お気軽に <a href="https://waffle.io/mogproject/easy-menu">GitHub Issue</a> に登録をお願いします。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-6879429799015808712015-08-23T20:18:00.001+09:002015-08-23T20:18:41.522+09:00Running Apache Spark Cluster by using Kubernetes<p>Kubernetes を使って Spark クラスタを立ち上げる</p>
<p> </p>
<h4>環境</h4>
<ul>
<li>Mac: OS X Yosemite Version 10.10.5</li>
<li>Vagrant: 1.7.4 => <a href="http://www.vagrantup.com/downloads.html">Download Vagrant - Vagrant</a></li>
<li>Virtual Box: 4.3.28 => <a href="https://www.virtualbox.org/wiki/Download_Old_Builds_4_3">Download_Old_Builds_4_3 – Oracle VM VirtualBox</a></li>
<li>Kubernetes: 1.0.1 (1.0.3 ではバグ発生)</li>
</ul>
<p> </p>
<h4>インストール</h4>
<p> </p>
<h5>Kubernetes のインストール (NG)</h5>
<p>適当なプロジェクト用ディレクトリに移動し、以下のコマンドを実行。</p>
<pre class="brush: plain; gutter: false;">$ export KUBERNETES_PROVIDER=vagrant
$ curl -sS https://get.k8s.io | bash</pre>
<p>早速コケた。</p>
<pre class="brush: plain; gutter: false;highlight:[24]">Downloading kubernetes release v1.0.3 to /proj/mogproject/example-spark/kubernetes.tar.gz
--2015-08-23 10:39:46-- https://storage.googleapis.com/kubernetes-release/release/v1.0.3/kubernetes.tar.gz
Resolving storage.googleapis.com... 216.58.220.176
Connecting to storage.googleapis.com|216.58.220.176|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 121767360 (116M) [application/x-tar]
Saving to: ‘kubernetes.tar.gz’
100%[===============================================================================>] 121,767,360 1.76MB/s in 65s
2015-08-23 10:40:51 (1.80 MB/s) - ‘kubernetes.tar.gz’ saved [121767360/121767360]
Unpacking kubernetes release v1.0.3
Creating a kubernetes on vagrant...
Starting cluster using provider: vagrant
... calling verify-prereqs
... calling kube-up
Using credentials: vagrant:vagrant
Bringing machine 'master' up with 'virtualbox' provider...
Bringing machine 'minion-1' up with 'virtualbox' provider...
==> master: Box 'kube-fedora21' could not be found. Attempting to find and install...
master: Box Provider: virtualbox
master: Box Version: >= 0
Request for box's Amazon S3 region was denied.
This usually indicates that your user account with access key ID
is misconfigured. Ensure your IAM policy allows the "s3:GetBucketLocation"
action for your bucket:
arn:aws:s3:::opscode-vm-bento</pre>
<p>どうやら vagrant up コマンドに失敗している模様。<br /> 認証周りを管理しているプラグイン vagrant-s3auth をアンインストールしたら、先に進んだ。</p>
<pre class="brush: plain; gutter: false;">$ vagrant plugin uninstall vagrant-s3auth</pre>
<p>しかし、今度はこの画面から一向に進まない。</p>
<pre class="brush: plain; gutter: false;">Waiting for each minion to be registered with cloud provider
..................................................................
</pre>
<p>1時間待っても、2時間待っても状況変わらず。</p>
<p>どうやら、こちらの certificate 関連の厄介な問題に直面したようだ。</p>
<ul>
<li><a href="https://github.com/kubernetes/kubernetes/issues/12892">Install not working on Mac OS X · Issue #12892 · kubernetes/kubernetes</a></li>
</ul>
<p>解決策は、1個前のバージョンである v1.0.1 にこちらのパッチを適用し、手動でインストールせよ、とのこと。</p>
<ul>
<li><a href="https://github.com/kubernetes/kubernetes/pull/12237/files">Automated cherry pick of #11390 upstream release 1.0 by jbeda · Pull Request #12237 · kubernetes/kubernetes</a></li>
<li><a href="http://stackoverflow.com/questions/32062868/kubernetes-with-vagrant-nodes-wont-register">docker - Kubernetes with Vagrant nodes won't register - Stack Overflow</a></li>
</ul>
<p> </p>
<h5>Kubernetes のインストール (リトライ)</h5>
<p>まずは VM を初期化。</p>
<pre class="brush: plain; gutter: false;">$ cd kubernetes
$ vagrant destroy -f
$ vagrant box remove kube-fedora21
$ cd ..
$ rm -rf ./kubernetes</pre>
<p>Version 1.0.1 を手動でダウンロードしてから、パッチを適用。その後、インストール。</p>
<pre class="brush: plain; gutter: false;">$ curl -LO https://github.com/kubernetes/kubernetes/releases/download/v1.0.1/kubernetes.tar.gz
$ tar zxvf ./kubernetes.tar.gz
$ cd kubernetes/cluster/vagrant
$ curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.0/cluster/vagrant/provision-master.sh
$ curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.0/cluster/vagrant/provision-minion.sh
$ cd ../..
$ export KUBERNETES_PROVIDER=vagrant
$ ./cluster/kube-up.sh
</pre>
<p>10分ほどで正常終了。ここまで長かった。</p>
<pre class="brush: plain; gutter: false;">Cluster validation succeeded
Done, listing cluster services:
Kubernetes master is running at https://10.245.1.2
KubeDNS is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kube-dns
KubeUI is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kube-ui
</pre>
<p>API との疎通確認</p>
<pre class="brush: plain; gutter: false;">$ ./cluster/kubectl.sh get pods
NAME READY STATUS RESTARTS AGE
</pre>
<p> </p>
<h5>Spark クラスタの起動</h5>
<p>Spark マスターサービスの起動</p>
<pre class="brush: plain; gutter: false;">$ ./cluster/kubectl.sh create -f ./examples/spark/spark-master.json
$ ./cluster/kubectl.sh create -f ./examples/spark/spark-master-service.json
</pre>
<p>暫く経つと Running になる。</p>
<pre class="brush: plain; gutter: false;">$ ./cluster/kubectl.sh get pods
NAME READY STATUS RESTARTS AGE
spark-master 0/1 Pending 0 1m
$ ./cluster/kubectl.sh get pods
NAME READY STATUS RESTARTS AGE
spark-master 1/1 Running 0 15m
$ ./cluster/kubectl.sh logs spark-master</pre>
<p>Spark ワーカーの起動</p>
<pre class="brush: plain; gutter: false;">$ ./cluster/kubectl.sh create -f examples/spark/spark-worker-controller.json</pre>
<p>15分経っても、3個中2個は Pending のままだった。</p>
<pre class="brush: plain; gutter: false;">$ ./cluster/kubectl.sh get pods
NAME READY STATUS RESTARTS AGE
spark-master 1/1 Running 0 34m
spark-worker-controller-6cgpd 0/1 Pending 0 15m
spark-worker-controller-tqa4b 0/1 Pending 0 15m
spark-worker-controller-yl7n2 1/1 Running 0 15m
$ ./cluster/kubectl.sh get services
NAME LABELS SELECTOR IP(S) PORT(S)
kubernetes component=apiserver,provider=kubernetes 10.247.0.1 443/TCP
spark-master name=spark-master name=spark-master 10.247.70.164 7077/TCP
$ ./cluster/kubectl.sh get nodes
NAME LABELS STATUS
10.245.1.3 kubernetes.io/hostname=10.245.1.3 Ready</pre>
<p>
</p>
<p> </p>
<h4>Spark を使う</h4>
<p>spark-master の IPアドレスとポートを確認。</p>
<pre class="brush: plain; gutter: false;highlight:3">$ ./cluster/kubectl.sh get service spark-master
NAME LABELS SELECTOR IP(S) PORT(S)
spark-master name=spark-master name=spark-master 10.247.70.164 7077/TCP</pre>
<p>Kubernetes の minion-1 にログインし、適当な Docker コンテナを起動。<br />
その中で、環境変数や /etc/hosts ファイルを書き換えるユーティリティ用スクリプト setup_client.sh を(同一シェル内で)実行。</p>
<pre class="brush: plain; gutter: false;">[vagrant@kubernetes-minion-1 ~]$ sudo docker run -it gcr.io/google_containers/spark-base
root@49cc6b98cb5f:/# . /setup_client.sh 10.247.70.164 7077</pre>
<p>Kubernetes 上の Spark クラスタへアクセスしていることを確認できた。</p>
<pre class="brush: scala; gutter: false;">
root@49cc6b98cb5f:/# spark-shell
15/08/23 11:04:18 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/___/ .__/\_,_/_/ /_/\_\ version 1.4.0
/_/
Using Scala version 2.10.4 (OpenJDK 64-Bit Server VM, Java 1.7.0_79)
Type in expressions to have them evaluated.
Type :help for more information.
Spark context available as sc.
15/08/23 11:04:31 WARN Connection: BoneCP specified but not present in CLASSPATH (or one of dependencies)
15/08/23 11:04:33 WARN Connection: BoneCP specified but not present in CLASSPATH (or one of dependencies)
15/08/23 11:04:43 WARN ObjectStore: Version information not found in metastore. hive.metastore.schema.verification is not enabled so recording the schema version 0.13.1aa
SQL context available as sqlContext.
scala> sc.isLocal
res0: Boolean = false
scala> sc.master
res1: String = spark://spark-master:7077
</pre>
<p>Python の API で、Spark ワーカーのホスト名を取得してみる。</p>
<pre class="brush: python; gutter: false;">root@49cc6b98cb5f:/# pyspark
Python 2.7.9 (default, Mar 1 2015, 12:57:24)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
15/08/23 11:12:06 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 1.4.0
/_/
Using Python version 2.7.9 (default, Mar 1 2015 12:57:24)
SparkContext available as sc, HiveContext available as sqlContext.
>>> import socket
>>> sc.parallelize(range(1000)).map(lambda x:socket.gethostname()).distinct().collect()
['spark-worker-controller-yl7n2']</pre>
<p> </p>
<h5>References</h5>
<ul>
<li><a href="http://kubernetes.io/v1.0/examples/spark/README.html">Spark example</a></li>
</ul>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0tag:blogger.com,1999:blog-5596861390145387686.post-9504270063856412032015-08-23T00:57:00.001+09:002015-08-23T01:11:04.481+09:00Python: Getting Started with NetworkX Graph Generator<p>NetworkX のグラフジェネレーターで遊ぶ</p>
<p> </p>
<p>out-of-the-box な機能が非常に豊富で感動した。</p>
<ul>
<li><a href="http://networkx.github.io/documentation/latest/reference/generators.html">Graph generators — NetworkX 1.9.1 documentation</a></li>
<li><a href="http://networkx.github.io/documentation/latest/gallery.html">Gallery — NetworkX 1.9.1 documentation</a></li>
</ul>
<p>せっかくなので、IPython Notebook でビジュアライズしてみる。</p>
<p> </p>
<h4>準備</h4>
<p>全て必須という訳ではないが、一応インストールしておく。</p>
<pre class="brush: plain; gutter: false;">pip install ipython jinja2 tornado pyzmq numpy scipy pylab matplotlib
pip install networkx</pre>
<p>作業ディレクトリに移動して、IPython Notebook 起動。</p>
<pre class="brush: plain; gutter: false;">ipython notebook</pre>
<p>ブラウザ上で操作し、 New Notebook を作成。</p>
<p><a title="Home" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM9V1ZLFeNKiA_eK-2f1LBuGkgtM5kbJ-0pN_nnAhYaUpHmD_hJMr_PvqWz9lkBWSmDC84KN45-wCYosywZjv58OQKflG15GlNXKaWvuNV-W3pvYbGCVptjeiu4PVc2JDqj5kfr_sOXrks/?imgmax=800"><img title="Home.png" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM9V1ZLFeNKiA_eK-2f1LBuGkgtM5kbJ-0pN_nnAhYaUpHmD_hJMr_PvqWz9lkBWSmDC84KN45-wCYosywZjv58OQKflG15GlNXKaWvuNV-W3pvYbGCVptjeiu4PVc2JDqj5kfr_sOXrks/?imgmax=800" alt="Home" width="400" height="auto" /></a></p>
<p> </p>
<h4>実行例</h4>
<p>コードの補完もできる。</p>
<p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_xuKlPqxMrTh5VdCYFSV76IfbyP-aZohzQnFwotzfdTQ0LnKeSMdVVkvZhFbSK0-h6NDUTQR6bPQLyG9F9FQkcHmrVOg3GPMAxzcc2evpwdDc-aUBW2TBG3SwOPUA6buSr0p87xowABLO/?imgmax=800" title="NetworkXGeneratorExample"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_xuKlPqxMrTh5VdCYFSV76IfbyP-aZohzQnFwotzfdTQ0LnKeSMdVVkvZhFbSK0-h6NDUTQR6bPQLyG9F9FQkcHmrVOg3GPMAxzcc2evpwdDc-aUBW2TBG3SwOPUA6buSr0p87xowABLO/?imgmax=800" alt="NetworkXGeneratorExample" title="NetworkXGeneratorExample.png" width="400" height="auto" /></a></p>
<p>ひととおりグラフを作ったあと保存し、そのファイルを Gist にアップロードしてみた。<br />
すると以下のとおり、簡単にコードとイメージを共有することができる。</p>
<script src="https://gist.github.com/mogproject/ef5473922411b11a566c.js"></script>
<p>このカジュアルさに、また感動。Python 素晴らしい。</p>mog projecthttp://www.blogger.com/profile/02753389440807192169noreply@blogger.com0