Python in Practice (PiP) の紹介


Python Advent Calendar 21日目の記事です．
遅れてしまって大変申し訳ありませんm(_ _;)m

本日は，今現在読み進めている "Python in Practice (PiP)" について紹介します．

## Python in Practice(PiP)とは？
[Python in Practice(PiP)](http://www.amazon.co.jp/Python-Practice-Concurrency-Libraries-Developers/dp/0321905636)は，Pythonのプログラミング力を向上させたいPythonistaのために書かれた本です．
2014年度の「Jolt Jolt Awards: The Best Books」にも選出されました．
参考：[この1年の優れたIT系書籍はどれか？ 「Jolt Awards: The Best Books」2014年版が発表される](http://www.publickey1.jp/blog/14/1itjolt_awards_20146.html)

> This book is aimed at Python programmers who want to broaden and deepen their Python knowledge so that they can improve the quality, reliability, speed, maintainability, and usability of their Python programs.
引用：p.1, l.1

本では以下の4つをテーマとして取り上げています．

- エレガントなコーディングのためのデザインパターン
- 並列処理やCythonを利用した処理速度の向上
- ハイレベルネットワーキング
- グラフィクス

本日は，この中から処理速度の向上に焦点をあてた"5.Extending Python"の章について紹介したいと思います．※内容を大分端折りますmm


## Extending Python
Extending Pythonの章では，Pythonの処理性能を向上させるいくつかのTipsについてまとめられています．

- PyPyを利用する
	- [PyPy](http://pypy.org)では，Built-in JIT(Just in Time compiler)を採用しており，処理に時間がかかるプログラムに関してはCPythonを利用するよりも圧倒的に実行時間が短くなります．
ただし，コンパイル時間の影響で逆に処理時間が短いプログラムに関しては実行時間が長くなったりするので注意が必要です．
- Time-criticalな処理に関してはCやC++を利用する
	- Pythonプログラムから参照できる形でCやC++のコードを書くことで，CやC++の持つ圧倒的な処理能力の恩恵にあずかれます．
CやC++のコードをPython中で利用する最もシンプルな方法は，[Python C interface](https://docs.python.org/3/extending/)を利用することです．
既にあるCやC++のライブラリを利用したい場合は，Python中でCやC++を利用するためのwrapperを提供している[SWIG](http://www.swig.org)や[SIP](http://www.riverbankcomputing.com/software/sip)といったツールを利用することが一般的です．C++を利用したい場合には，[`boost::python`](http://www.boost.org/libs/python/doc/)を利用することも出来ます．
この分野の最新情報については，[CFFI(C Foreign Function Interface for Python)](https://bitbucket.org/cffi/cffi)も御覧ください．
- Cythonを利用して，PythonコードをCのコードへコンパイルする
	- [Cython](http://cython.org)は，Pythonをベースとして，静的なデータ型を扱えるように拡張された言語です．CythonのソースコードはC/C++のコードとして翻訳され，Pythonの拡張モジュールとしてコンパイルされます．CythonはほとんどのPythonコードをコンパイル出来，かつ書かれたコードは大抵の場合かなり実行速度が向上するため，高速化を意識した場合非常に有用なツールです．
	- 詳細は公式ページを参照して下さい（手抜き）
- `ctypes`を利用してCのライブラリにアクセスする

この中から，`ctypes`を利用してCのライブラリにアクセスする手法に焦点を絞って，詳しい利用方法を紹介します．

### Accessing C Libraries with ctypes
Pythonの標準モジュールの一つである`ctypes`は，CやC++で書かれたstand-aloneな共有ライブラリへのアクセスを可能にします．(Linuxでは`.so`，OS Xでは`.dylib`，Windowsでは`.DLL`で表現されます．)

実際に，`ctype`モジュールの使い方を見ていきます．
ここでは，例として，与えられた単語に綴りを表すハイフンを挿入する[`hyphen`](http://sourceforge.net/projects/hunspell/)ライブラリを利用します．（このライブラリ自体はOpenOffice.orgやLibreOfficeで利用されています．）
e.g. input: extraordinary, output: ex-traor-di-nary

具体的には，`hyphen`ライブラリ中の以下の関数を利用します．（各関数の詳細な説明は割愛します．）

```hyphen.h
// ハイフン処理用の辞書ファイルからHyphenDictポインタを作成する
HyphenDict *hnj_hyphen_load(const char *filename);

// メモリ解放用
void hnj_hyphen_free(HyphenDict *hdict);

// HyphenDictポインタに従ってwordをハイフン処理する
int hnj_hyphen_hyphenate2(HyphenDict *hdict, const char *word, int word_size, char *hyphens, char *hyphenated_word, char ***rep, int **pos, int **cut);
```

早速Pythonでこのライブラリを利用してみましょう！

まず利用する共有ライブラリ`hyphen`を探します．
※正しく導入されている場合，pathの通っている場所にLinuxでは`libhyphen.so`，OS Xでは`hyphen.dylib`，Windowsでは`hyphen.dll`といったファイルが存在しているはずです．

```Hyphenate1.py
import ctypes

class Error(Exception): 
    pass

_libraryName = ctypes.util.find_library("hyphen")
if _libraryName is None:
    raise Error("cannot find hyphenation library")

_LibHyphen = ctypes.CDLL(_libraryName)
```

シンプルなのであまり説明する必要はなさそうですが，`ctypes.util.find_library()`関数が共有ライブラリを探していて，`ctypes.CDLL()`関数で読み込んでいます．
ライブラリを読み込んだら，ライブラリ中の関数のPython wrappersを作成します．ライブラリ中の関数をPythonの変数に割り当てる方式が一般的です．
変数に関数を割り当てた後，引数のtypeやreturnのtypeを指定する必要があります．

e.g. `hnj_hyphen_load`の例

```Hyphenate1.py
_load = _LibHyphen.hnj_hyphen_load
_load.argtypes = [ctypes.c_char_p]
_load.restype = ctypes.c_void_p
```

e.g. `hnj_hyphen_hyphenate2`の例

```Hyphenate1.py
_int_p = ctypes.POINTER(ctypes.c_int)
_char_p_p = ctypes.POINTER(ctypes.c_char_p)

_hyphenate = _LibHyphen.hnj_hyphen_hyphenate2
_hyphenate.argtypes = [
    ctypes.c_void_p,                # HyphenDict *hdict
    ctypes.c_char_p,                # const char *word
    ctypes.c_int,                   # int word_size
    ctypes.c_char_p,                # char *hyphens
    ctypes.c_char_p,                # char *hyphenaated_word
    _char_p_p,                      # char ***rep
    _int_p,                         # int **pos
    _int_p                          # int **cut
]
_hyphenate.restype = ctypes.c_int
```

これらを使って，PythonのPrivateな関数を作成してみます．

```Hyphenate1.py
def hyphenate(word, filename, hyphen='-'):
    originalWord = word
    
    hdict = _get_hdict(filename)
    word = word.encode("utf-8")
    word_size = ctypes.c_int(len(word))
    hyphens = ctypes.create_string_buffer(word)
    hyphenated_word = ctypes.create_string_buffer(len(word) * 2)
    rep = _char_p_p(ctypes.c_char_p(None))
    pos = _int_p(ctypes.c_int(0))
    cut = _int_p(ctypes.c_int(0))

    if _hyphenate(hdict, word, word_size, hyphens, hyphenated_word, rep, pos, cut):
        raise Error("hyphenation failded for '{}'".format(originalWord))

    return hyphenated_word.value.decode("utf-8").replace("=", hyphen)
```

こんな感じ．`ctypes.create_string_buffer`はbyte数を基にCの`char`を作成する関数です．
ハイフン処理用の関数にはUTF-8にのbyteを渡す必要があるため，エンコード処理が行われています．

`_get_hdict()`関数は以下の通リ書けます．
単純なファイルのload処理です．

```Hyphenate1.py
_hdictForFilename = {}

def _get_hdict(filename):
    if filename not in _hdictForFilename:
        hdict = _load(ctypes.create_string_buffer(filename.encode("utf-8")))
        if hdict is None:
            raise Error("failed to load '{}'".format(filename))
        _hdictForFilename[filename] = hdict
    hdict = _hdictForFilename.get(filename)
    if hdict is None:
        raise Error("failed to load '{}'".format(filename))
    return hdict
```

CのライブラリをPythonから呼び出す準備が出来ました．
実際に関数を使ってみると以下のような出力が得られるはずです．

```sh
>>> hyphenate('extraordinary', '/path/to/dictfile')
u'ex-traor-dinary'
```

このように，PythonからカジュアルにCのライブラリが利用できるので，処理がどうしても重くなる部分に関してはCのライブラリに処理を任せるといった事を検討されても良いかもしれません．

## まとめ
今回は，PiPの中からC言語拡張の部分をピックアップして紹介しました．
PiPは，非常に簡単な英語で書かれているため，英語が苦手という方にもオススメの本です．
特に最初のデザインパターンの章は，言語横断的な基礎の話なので，他言語を使っている方にも参考になる話が多いかと思います．

年明けに，PyLadies Tokyoでこの本の読み会をしようかと計画してますので，興味ある方がいらっしゃいましたらご連絡下さい（宣伝）