Plant seeds

露頭に迷ってしまいそうな大学院生が、どこかに書かなければ忘れてしまいそうな大切なことをここに残していきます。

Python/C API (通称, CPython) 学習記録

フィンランドに滞在中に書いた書きかけの記事ですが、少しでも誰かの役に立てばと思い、公開することにします。


リファレンスとちょっとした例しか出てこなかったので、あまりよく分からない人向けに書いてみようと思う。

別の研究室で研究させてもらっているときにもともとあるものがCPythonであったために勉強せざるをえなくなった。 Cの知識はまあまああって、C++は授業でかじった程度で、あまりよくわかってない。

いざ調べてみるとほとんど英語の資料で、日本語のものは少なかったのであえて日本語の記事を書いてみようと思う。

参考にしたのは http://docs.python.jp/3.5/c-api/intro.html であるが、訳が微妙で、逆にこれが何の英語の訳なのかを考えるハメになって、翻訳の本質が何なのかを考えてしまうほどである。

とりあえず、APIという用語について知らない人は調べるといい。 このAPIは、昔はC/C++で開発していた便利なものがあったが、Pythonの方が簡単に実装できそうだけど、書き換えるのが億劫だ、または、機械学習計算などを使っていてここの部分がどうしても速くならない。なんとかならないものかというときに、C/C++のメリットをいいとこどりしたいときに使える。

もしなぜこのAPIを使っているのかがまったくわからなかったら、必ず作った人に聞いた方がよい。どんな目的でこれを使っているのかがわかっていないと、不必要なところまでこのAPIを使ってしまったりするからだ。このAPIは少なくともPythonほど簡単ではないから時間を無駄にしてしまう。

まず、基本として.cpp, .c形式のファイルの先頭に [c]

include "Python.h"

[/c] とあるものは、これに該当する。 このAPIがこのヘッダーファイルで定義されているからだ。

命名規則として_PyかPyが接頭辞としてついているもの(例:PyObject)はこのAPIのものだけにしか使わない。

このAPI関数はPyObject型の引数+α+PyObject型の返り値を持ちます。 PyObjectは任意のPythonオブジェクトを表現するためのものです。 これらはすべてヒープ上に置かれるため、auto, static宣言はできません。 唯一の例外として型オブジェクトはメモリ解放ができない決まりになっているので、staticなPyTypeObjectを使うことになります。

さらに、PyObjectには、必ず型と参照カウントを持っていなければなりまsねん。 逆に言えば、整数型でも、参照カウントを持っている必要があります。Cの場合はアドレスだけで十分ですよね。

また、これには型を調べるマクロなどがあり、PyList_Check(a) などを使うことで、aが参照しているオブジェクトの型を調べることができます。この場合はaが指し示すオブジェクトがリストだったときにTrueを返すことで判断できます。

さきほど登場した、参照カウントというワードはナンジャラホイということで、これから説明します。 メモリについて知らない人は調べてください。ここでは説明しません。 当たり前ですがメモリは有限で、もし無限だったらこんな機能は不要です。 なぜなら、使っていないオブジェクトをそのまま放置しておくのがメモリの効率に悪い影響を与えるからです。 自分のオブジェクトを必要としているオブジェクトがどれくらいあるのかを記録しておくことで、自分が必要なのかをわかるためです。 簡単な例で考えてみましょう。 ある会社では机が3つしかありません。 社長は3つのうちの1つの机をすでに使っています。 そこで、エクセルが使える人が入社してきて、社長がこれを全部エクセルにしてくれと頼みます。 エクセル君は頑張って作業しています。 次に、ワードが使える人が入社してきます。社長はエクセルのデータを使ってワードでまとめてくれと頼みます。 そして、エクセル君のやることがなくなりました。 次に、パワーポイント君が入社してきて、この2人が作ったデータをプレゼン資料にしてくれと頼みます。 が、机は3つしかないので作業できません。 エクセル君は疲れて寝てしまっていて、起こすこともできません。

この無駄はどうすればよかったのでしょうか。 そうです、エクセル君は誰に必要とされているのかを記録しておけばよかったのです。 エクセル君のやることがなくなったら、「君はもう帰っていいよ。ご苦労様。」といえばいいのです。

これが参照カウントです。

どうやって、参照カウントを操作すればよいでしょうか。 これがPy_INCREF()とPy_DECREF()です。 http://docs.python.jp/3.5/c-api/refcounting.html Py_DEFREF()で参照がなくなったことがわかった場合、自動的にメモリ解放を行ってくれるようです。