Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

Python3 Advent Calendar - Pythonで2/3両方で動くコードを書く(C/API)

これは Python3 Advent Calendarの記事です。
こんにちわ、高校生です。
先日、子供(遼くん)が無事生まれましたが、毎日お世話で忙しいです。

Python3 Advent Calendar ということでPython3に関して書いてみたいと思います。
Python3対応というのはまあいろいろあるんですが、Pure Pythonで両方動くコード書こうとなると文法とかいろいろ面倒ですね。
ですがC/APIで書くとマクロで大きく処理を2系、3系と切り替えができるのでので容易に両バージョンをサポートするコードが書けます。
ということで2/3両方をサポートするコードをC/APIを中心に幾つか書いてみたいと思います。
(3系といっても3.2以降と思ってください)

Python3の判別

Python3であるかどうかは元々用意されているPythonバージョンを確認できるマクロで容易に判断できます。

例:Python3系

#if PY_MAJOR_VERSION >= 3
#define PY3
#endif

単純に3系の場合にはこんな感じですね。

PythonのC/APIをCで利用する際のAPIが3.1以降変わったため、それらを考慮するならばこのような書き方になります。

例:Python3.1以降

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) || PY_MAJOR_VERSION > 3
#define PY3
#endif

PY_MINOR_VERSIONの他にも、HEXバージョンで判断する方法もあります。
(多分HEXが一番楽かも)
毎回、長いマクロ名を書くのがだるいので共通ヘッダなどに短めに再定義してしまうと楽だと思います。

モジュールの定義

モジュールの作成方法も2/3で変わっています。
ここでは先程の判定マクロを使用して2と3で分岐しています。

例:書き方の例

static PyMethodDef CoreMethods[] = {
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


#ifdef PY3
#define INITERROR return NULL

static struct PyModuleDef core_module_def = {
    PyModuleDef_HEAD_INIT,
    MODULE_NAME,
    NULL,
    -1,
    CoreMethods,
};

PyObject *
PyInit_core(void)
#else
#define INITERROR return

PyMODINIT_FUNC
initcore(void)
#endif
{
    PyObject *m;

#ifdef PY3
    m = PyModule_Create(&core_module_def);
#else
    m = Py_InitModule3(MODULE_NAME, CoreMethods, "");
#endif
    if(m == NULL){
        INITERROR;
    }
...
#ifdef PY3
    return m;
#endif

}

大きく異なるのはPy_InitModule3などがなくなった点です。
モジュール定義用の構造体にモジュール情報をセットしてPyModule_Createでモジュールを作成します。
またPython3以降は作成したモジュールオブジェクト返さないといけません。
(返り値が異なる。初期化メソッド名なども変更)
その他、モジュールの関数の定義などは特に変更はないです。

PyInt系関数の廃止

Python3からはPyInt_から始まる関数は排除されています。
Python3からは全てPyLongに統一する必要があります。
特に問題なければ2/3でもPyLongの関数を使ってしまってもいいかも知れませんね。
生成だけではばくチェックなどもPyLongの関数に変更になります。

#ifdef PY3
    if(!PyLong_Check(sec)){
#else
    if(!PyInt_Check(sec)){
#endif

文字列の生成

色々なところで触れられていますが、Python3では通常文字列は全てUnicode型になります。
今までのstr型と同じような動作にしたい場合にはPyBytes系(PyString)の関数を使用することになります。

    char *tmp;

...

#ifdef PY3
    return PyUnicode_FromString(tmp);
#else
    //あるいはreturn PyString_FromString(tmp);
    return PyBytes_FromString(tmp);
#endif

Python2.6以降はPyBytes系の関数が既に存在しているため、そちらを使う事を推奨します。
PyUnicode_FromStringは渡された文字列がUTF-8 encodedなものとして処理をします。多くの場合はこれで事足りると思います。

BytesとUnicode

Python3でも全てUnicodeを使えばいいのか?というとそうではありません。
生データを必要とする場合には、適時PyBytes_FromStringでBytesを返す必要があります。

例:

  1. ファイルからのデータの読込結果
  2. ソケットから読み込んだデータ

興味があればPEP-3333などを読むと使い分けに対する理解が深まるかも知れません。

char*型の参照

逆にPythonの文字列型をCで参照する場合にもUnicodeAPIを使います。

    char *ch;
    char *tmp;
....

#ifdef PY3
    PyObject *latin_item;
    latin_item = PyUnicode_AsLatin1String(ip);
    ch = PyBytes_AsString(latin_item);
    Py_DECREF(latin_item);
#else
    ch = PyBytes_AsString(ip);

#endif

上記の例はlatin1のUnicodeである前提で処理しています。
(ネットワーク系の処理ではこれで十分だったりします。)
必要に応じてPyUnicode_Decode関数でデコードし、参照する必要があります。
上記ではUnicodeかどうかのチェックをしていませんが、必要に応じてPyUnicode_Checkを呼び出して下さい。

C API の提供、およびImport

あまり使われてないようですがC/APIで書かれたモジュールは他のCモジュールでも使用できるAPIを提供することができます。
2.7および3.2以降からは以前のPyCObject_FromVoidPtrではなくPyCapsule_系のAPIを使用します。

例:提供する側

#if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) 
#define USE_PYCAPSULE
#endif

...

        PyObject *c_api_object;
        static void *_C_API[8];
        ....

#ifdef USE_PYCAPSULE
        c_api_object = PyCapsule_New((void *) _C_API, "xxxxx._C_API", NULL);
#else
        c_api_object = PyCObject_FromVoidPtr((void *) _C_API, NULL);
#endif
        if (c_api_object != NULL)
        {
                PyModule_AddObject(m, "_C_API", c_api_object);
        }

例:使用する側

#ifdef USE_PYCAPSULE
#define C_API_Import() \
{ \
        _C_API = (void**)PyCapsule_Import("xxxxx._C_API", 0); \
}
#else
#define C_API_Import() \
{ \
        PyObject *module = PyImport_ImportModule("xxxxx"); \
        if (module != NULL) { \
                PyObject *c_api_object = PyObject_GetAttrString( \
                        module, "_C_API"); \
                if (c_api_object != NULL && PyCObject_Check(c_api_object)) { \
                        _C_API = \
                                (void **) PyCObject_AsVoidPtr(c_api_object); \
                        Py_DECREF(c_api_object); \
                } \
                Py_DECREF(module); \
        } \
}
#endif

Pure PythonでPython3を判別する

Python側でも簡単に判別することができます。
マクロ同様、短めの関数名で定義しておくと使うとき楽だと思います。

import sys

def is_py3():
    return sys.hexversion >=  0x3000000

モジュール定義時に大きく分岐させることで両方で動くモジュールを作成することが出来ます。

例:

if is_py3():

    #python3系の処理を書く

    class socket(_socket.socket):
	....
else:

    class socket(_socket.socket):
	....

とざっくり両バージョンで動かすためのコードを紹介しました。
両方をサポートするコードを書いた場合にはtoxなどでテストをすることをオススメします。
(toxについてはそのうち誰かが書くと思います)

次はid:doloopwhileさんです。

関数などの実行結果をすりかえる

こんにちは、Python界のヘンリー塚本ことmopemopeです。
なんかpythonのpycファイルをいじってアタックするとかセキュリティの話が出てきてますね。

せっかくなので別の方法でこーいうこともできますよというのを紹介しておきます。
もちろん普通の人は書かないであろう、あらびきな方法です。

今回は関数などの実行結果をすりかえるという話です。
ではコード。

steal.h
#ifndef STEAL_H
#define STEAL_H

#include <Python.h>

#ifdef DEVELOP
#define DEBUG(...) \
    do { \
        /*printf("%-22s%4u: ", __FILE__, __LINE__);*/ \
        printf("%-22s %-32s%4u: ", __FILE__, __func__, __LINE__); \
        printf(__VA_ARGS__); \
        printf("\n"); \
    } while(0)
#define RDEBUG(...) \
    do { \
        /*printf("%-22s%4u: ", __FILE__, __LINE__);*/ \
        printf("\x1B[31m%-22s %-32s%4u: ", __FILE__, __func__, __LINE__); \
        printf(__VA_ARGS__); \
        printf("\x1B[0m\n"); \
    } while(0)
#else
#define DEBUG(...) do{}while(0)
#define RDEBUG(...) do{}while(0)
#endif

#define MODULE_NAME "evalsteal"

#if __GNUC__ >= 3
# define likely(x)	__builtin_expect(!!(x), 1)
# define unlikely(x)	__builtin_expect(!!(x), 0)
#else
# define likely(x) (x)
# define unlikely(x) (x)
#endif

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) || PY_MAJOR_VERSION > 3
#define PY3
#endif

#if PY_MAJOR_VERSION < 3
#ifndef Py_REFCNT
#  define Py_REFCNT(ob) (((PyObject *) (ob))->ob_refcnt)
#endif
#ifndef Py_TYPE
#  define Py_TYPE(ob)   (((PyObject *) (ob))->ob_type)
#endif
#endif


#endif

これは僕がテンプレで使ってるヘッダなのであんまし意味ないです。

steal.c
#include "steal.h"
#include "structmember.h"
#include <frameobject.h>
#include <opcode.h>

static PyObject* retObj = NULL;

static int
run(PyFrameObject *f, PyObject *result, int entering)
{
    PyObject** p;
    int r = 1;
    PyCodeObject* co = f->f_code;

    int new_i = PyString_GET_SIZE(co->co_code) - 1;
    while (PyString_AS_STRING(co->co_code)[new_i] != RETURN_VALUE){
        --new_i;
    }
    new_i -= entering;
    f->f_lasti = new_i;
    f->f_iblock = 0;

    for (p=f->f_stacktop; --p >= f->f_valuestack; ) {
        Py_XDECREF(*p);
        *p = NULL;
    }
    p = f->f_valuestack;
    *p++ = result;
    f->f_stacktop = p;
    return r;
}

static int
do_trace_or_profile(PyObject *v, PyFrameObject *f, int what, PyObject *arg)
{
    int r = 0;

    if(what == PyTrace_CALL){
        f->f_tstate->use_tracing = 1;
        f->f_tstate->tracing--;
        r = run(f, retObj, what == PyTrace_CALL);
        f->f_tstate->tracing++;
    }
    return 0;
}


static void
set_profile(PyThreadState* tstate, Py_tracefunc func, PyObject* arg)
{
	PyObject *temp = tstate->c_profileobj;
	Py_XINCREF(arg);
	tstate->c_profilefunc = NULL;
	tstate->c_profileobj = NULL;
	tstate->use_tracing = tstate->c_tracefunc != NULL;
	Py_XDECREF(temp);
	tstate->c_profilefunc = func;
	tstate->c_profileobj = arg;
	tstate->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL);
}

static PyObject*
evalsteal_steal(PyObject *self, PyObject *arg)
{
    retObj = arg;
    PyThreadState* tstate = PyThreadState_Get();
	if (tstate->c_profilefunc == NULL) {
		set_profile(tstate, &do_trace_or_profile, NULL);
	}
    Py_RETURN_NONE;
}

static PyMethodDef EvalStealModMethods[] = {
    {"steal", evalsteal_steal, METH_O, 0},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

#ifdef PY3
#define INITERROR return NULL

static struct PyModuleDef eavlsteal_module_def = {
	PyModuleDef_HEAD_INIT,
	MODULE_NAME,
	NULL,
	-1,
	EvalStealModMethods,
};

PyObject *
PyInit_evalsteal(void)
#else
#define INITERROR return

PyMODINIT_FUNC
initevalsteal(void)
#endif
{
    PyObject *m;

#ifdef PY3
	m = PyModule_Create(&evalsteal_module_def);
#else
    m = Py_InitModule3(MODULE_NAME, EvalStealModMethods, "");
#endif
    if(m == NULL){
        INITERROR;
    }

#ifdef PY3
	return m;
#endif
}

setup.pyは割愛します。

まあPyEval_SetProfileとかでもいいですけど、カレントスレッドだけで。
ceval.cで評価するフレーム内のスタックをバンバン捨てて任意の値を積みます。
「ceval.c涙目wwww」みたいなコードですね。

import evalsteal
# return -1
evalsteal.steal(-1)

def func(a, b):
    sum_val = a
    for i in xrange(b):
        sum_val += i
    return sum_val

result = func(0, 10)
print("result %d" % result)

本来ならば45が返りますが、実行結果は

result -1

となります。
悪用するとまあいろんな事ができますが、有用な使い方としてはceval.cを再実装することができます。
あとはわかるな?

覚悟を決める 新卒準備カレンダー 2011春

このエントリーは新卒準備カレンダー 2011春 : ATNDという、みんなで仕事に関して自分が考えることなどをエントリーに書いていく企画で書かれたものです。
事の発端は某所でgdgd言ってた話が膨らんでこーなったわけですが、まあその場に居合わせてのもあり何か書いてみようと思います。
はっきり言ってみんなに書きたい事が書かれてしまってるので適当です。

震災に関しては他で散々言われてますので敢えて触れません。

お前、誰よ?

@mopemope:松原 豊と言います。株式会社 Abby CTOやってます。大掃除では便所掃除を担当しています。
一時期、Seasar界隈のコミッタをしていた事もありますが、今はほぼpython界隈の人間です。
アウトプットとしてはまあなんか本とかjavaの雑誌にチラ裏みたいな事書いたりしてます。
他にも

  • PEP249の実装(pymysql、mysqlのDBドライバ)
  • PEP333のサーバ実装(wsgi server、meinheld)

など主要なPEPの実装をしてたりします。

この業界に入ったきっかけ

まあ新卒向けなので自分の時はどうだったかなというのを少し書いておきます。
僕は高校生の時からずっとバンドをやっていて、コンピューターに触れる機会も全然ありませんでした。
大学生の頃にはもうライブハウスに半分勤めてるような状態で、「まあ卒業しても最悪このままライブハウスで働くかな」といった感じでした。
それもあってか、就職活動はほぼしていませんでした。
当時2000年頃はいわゆる就職氷河期ってやつで就職活動してもまともな会社にはなかなか入れないし、就職先も工場ばっかだったせいもあります。
工場で働くのは抵抗があったので、結局卒業するまで就職先が決まってない状態でした。
まあよくある話です。
とはいえまあカタギの仕事をしてないとまずいかなと思い、その辺に売ってる就職情報誌を見て工場以外の職種に適当に電話をしてみたりしていました。
で運よく派遣PGとして潜り込むことに成功というわけです。
世の中なんとかなるもんだなと思います。

覚悟を決める

とりあえずこの職業に就いた方には是非覚悟をしてもらいたいです。
プログラマは常に新しい事を覚えなければならない職業です。この職業についてる限りそれがずっと続きます。
私が言いたいのはこの「ずっと続く」という所に関してどれくらい認識、覚悟してるのか?という点です。
もし、覚えることがめんどいとか、勉強が苦手、苦痛という人は無理にこの職業につかない方がいいでしょう。
もちろん中にはそういった事を考えなくても済むような根っからのプログラマ体質の人もいるでしょう。
(そういった人は非常に少ないです)

私は一応この職業について10年超えてますが、今でも勉強しています。
もちろんずっと続けるにはそれなりに工夫もしていかないといけないでしょう。
続かせるためにどうするか?という事も考えていかなくてはなりません。

本当に勉強しないといけないのか?

さて、本当に勉強しないといけないのか?という点ですが、しないといけません。
メシを食っていくという点でいけば。

もちろん、旧来の方法でも出来ることは多々あります。
ですが、新しい方法は旧来よりも様々な事が改善されています。
(まあ新しい方法なので当たり前ですけど)
まあ改善内容は効率、ミスをしにくくなるなどありますが一番大きいのは所要時間でしょう。
同じ仕事するのに長い時間がかかる人と短時間でミスも少ない人だとどっちに仕事をさせたいですか?
勉強していない、効率の悪いPGは基本的に仕事がもらえなくなり食っていけなくなります。

僕はひとつぐらいスクリプト言語を覚えておくことをお勧めします。
やっつけ仕事をサッと終らせるためにeclipseを立ち上げるとかオーバースペックです。
下手すると起動する間に仕事を終らせることも可能です。

動機付け

まあとは言え勉強し続けるモチベーションを確保するのはそれなりに大変です。
結局どう動機付けしていくかが問題です。
パターンとしては以下のパターンぐらいしかないでしょう。

  • 楽したい
    • 楽な方法ないの?
    • 遊びたいからさっさとこの仕事終らせる方法ないの?
  • 目立ちたい
    • 一番最初にこれをやったら目立てる
    • 日本でxxxの第一人者になりたい
  • 興味がある
    • おもしろそう
    • xxxを作ってみたい

この中からうまく選択していけばいいと思います。
ただxxxを作ってみたいシリーズは注意です。
漠然と思ってるだけでは全くもって無意味です。
少しでも自分で調べ、書けるとこだけでも書いてみることをオススメします。
完成しなくても構いません。書いて残しておく事が大切です。
少しでも書くのと書かないのでは大違いです。
真面目にやっていれば自分がレベルアップし、続きが書けるようになります。
いきなりエベレストに登ろうとしても無理なようにチマチマ経験を重ねることが大事です。

やりたいときが始め時

新卒時から覚悟しておくことがベストですが、やって遅すぎるという事はありません。
特に年齢は気にしなくてもいいです。
やりたいことがあれば今すぐはじめればいいでしょう。
私もCでHTTPサーバー書いたのは30過ぎてからなので全然問題ないでしょう。

問題はやるやる詐欺の方です。
よく「xxxxについてやってみたい」みたいな話を聞きますが、今のご時世ほとんどの事は始めようと思えば今すぐ始めれるはずです。
(もちろんそのために色々とやってることがあれば問題ないでしょう)
やらない理由探しをせず、うまくやる方向に自分を持っていくべきでしょう。

途中結果のコードなどは必ず捨てずに取っておくと良いです。
それがヒントで先に進めるような事もあったりするので。
Dropboxなどで共有しておくとどこからでも読めるのでオススメです。

オススメの書籍

僕は技術書の前に考え方を身につけるために、ロジカル・シンキング、クリティカル・シンキング系を本をまず読むことをお勧めします。
考え方の基本を学んだ方が何をやるにも効率があがるので基礎力(思考力)を上げるようなものを読むことお勧めします。
土台が違うと将来ものすごく差が出ます。

最後に

今回の震災では大変多くの方々が被害にあわれてると思います。
ですが、幸いにもネットというインフラは一部を除いて機能しています。
我々エンジニアが作った物も安否確認に役に立ったり、情報を整理するために使われたりと色んな事に役に立っています。
そういった方向でも支援することができるという事を覚えておいて下さい。
キツイ職業というイメージではなく色んな事で役に立てる職業である事を覚えておいて下さい。

また今回の震災で亡くなった方々にご冥福をお祈りします。

次は@xuwei_kさんの予定です。

pyearthquakeで震源地をplotする

先日の地震に関連して。
pythonであればものすごく簡単にplotできる。
データの元ネタはUSGS

install

sudo easy_install pyearthquake

plotする

M6.0以上のものをplotする

from pyearthquake import *
catalog = usgs.retrieve_catalog("M1+PAST_7DAY")
mag6_list = [event for event in catalog if float(event["Magnitude"]) >= 6.0]

usgs.plot_events(mag6_list)

参照
http://pyevolve.sourceforge.net/wordpress/?p=1471

web2pyを使おう!

おはようございます!小倉智昭です!
ちんこうp!ちんこうp!
いやあid:moriyoshiパイプカットするとかしないとかで盛り上がってる中恐縮なんですが今回は
@aodag先生から、アドベントカレンダーが回ってきたのでその話をしたいと思います。

web2py

webフレームワークということなので今回はweb2pyを紹介したいと思います。
正直書くことがなくて困ってたので「ドキッ!漢字だらけのwebサーバ、eurasia」の話を書こうかと
思ったですがやめました。
漢字読めないし。

で話を戻します。
よくpython初心者に使うwebフレームワークなら何がいいですけねー?みたいな話がありますが、
もちのろん答えは決まっています。pyramidじゃなくweb2pyです。
djangoなんて難しいので使ってられません。

web2pyはシンプルにwebアプリケーションを書けるフレームワークです。
シンプルに処理を記述できるため、海外ではそこそこ人気があります。
またドキュメントも整備されています。

web2pyのサイトは以下です。
http://web2py.com/

ここからweb2pyをダウンロードできます。
http://web2py.com/examples/default/download
windows版もちゃんとあります。親切ですね。

では早速起動してみましょう。
1行もコード書いてないですよね?不安ですか?でも安心してください。
動きます。
GUIのツールが立ち上がったらadminのパスワードを入れて、start serverボタンを押しましょう。
初期画面が出てきましたね。

web interface

web2pyの特徴はwebからアプリケーションコードが書けることです。
しかも記述する内容もとてもシンプルで済みます。

adminからアプリケーションを作成、実装してみましょう。
初期画面からclick here for the administrative interfaceをクリックして

http://127.0.0.1:8000/admin/default/site

にアクセスしてみましょう。

左のNew application wizardをクリックすると新しいアプリケーションを作れます。
生成できる雛形をいろいろ編集することができます。
作成したらadminからeditを押して編集できます。

Controllersから新しいContorollerを作成します。
今回はhelloにします。
出来たControllerのeditを押すとエディタ画面に遷移します。
では実装します。といっても最初からコードが書いてあったりします。

# coding: utf8
# try something like
def index(): 
    return dict(message="hello from hello.py")

編集画面ではCtrl-Sを押すと保存できます。
かなり凝ってますね。
次にテンプレートであるViewを作成します。
EDIT APPLICATIONのページに戻り、Viewsからhello/index.htmlを追加します。

{{extend 'layout.html'}}
<h1>{{=message}}</h1>

web2pyではシンプルにメソッド名がurlになります。

http://127.0.0.1:8000/hello/hello/index

にアクセスしてみましょう。
hello from hello.pyと表示できましたか?

最後に

まあざっくり早足で紹介しました。
このおもちゃのようにみえるweb2pyですが、提供している機能はそれなりにしっかりしています。

  1. Form
  2. Validation
  3. Models and Migrations
  4. Cache

また基本的にweb上からなんでもできます。
テストなんかもweb上から走らせられます。すごいですね。

ちょっとしたwebアプリケーションを作成する際に是非使ってみて下さい。
次はid:kuenishi先生にお願いしようと思います。

参考:
web2py - Preface
Google グループ
Google グループ
http://web2py.com/examples/default/videos

omake

Pythonなんてしらねーよ!て人向けにシンプルなwsgiアプリケーションの例をあげておきます。
Python C/APIで拡張モジュールを書くときにMakefile書くとかそーいう嘘情報は無視してください。

setup.py
try:
    from setuptools import Extension, setup
except ImportError:
    from distutils.core import Extension, setup

import os
import sys
import os.path
import platform

def read(name):
    return open(os.path.join(os.path.dirname(__file__), name)).read()


setup(name='hellowsgi',
    version="0.1dev",
    description="",
    long_description=read('README.rst'),
    author='yutaka matsubara',
    author_email='yutaka.matsubara@gmail.com',
    license='',
    platforms='',
    install_requires=[
    ],
    
    entry_points="""

    """,
    ext_modules = [
        Extension('hellowsgi',
            sources=['src/hello.c',],
            )],

    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Programming Language :: C',
        'Programming Language :: Python',
    ],
)

hello worldを出してみましょう。

#include <Python.h>

static inline PyObject*
hellowsgi_hello(PyObject *self, PyObject *args)
{
    PyObject *environ, *start_response, *call_args, *status;
    PyObject *headers, *response;

    if (!PyArg_ParseTuple(args, "OO:hello", &environ, &start_response)){
        return NULL;
    }

	if (!PyDict_Check(environ)){
        PyErr_SetString(PyExc_TypeError, "environ must be a dict object");
        return NULL;
    }

    if(!PyCallable_Check(start_response)){
        PyErr_SetString(PyExc_TypeError, "start_response must be callable");
        return NULL;
    }

    headers = PyList_New(0); 
    PyObject *field = PyString_FromString("Content-type");
    PyObject *value = PyString_FromString("text/html");
    PyObject *header = Py_BuildValue("(OO)", field, value);
    
    Py_DECREF(field);
    Py_DECREF(value);

    PyList_Append(headers, header);
    
    Py_DECREF(header);

    status = PyString_FromString("200 OK");
    call_args = Py_BuildValue("(OO)", status, headers);
    Py_DECREF(status);
    Py_DECREF(headers);

    PyObject_CallObject(start_response, call_args);
    
    Py_DECREF(call_args);
    
    response = PyList_New(0); 
    PyObject *o = PyString_FromString("hello world!");
    PyList_Append(response, o);
    Py_DECREF(o);

    return response;
}

static PyMethodDef hellowsgi_methods[] = {
	{"hello", hellowsgi_hello, METH_VARARGS, ""},
	{NULL, NULL} /* sentinel */
};

PyMODINIT_FUNC
inithellowsgi(void)
{
    PyObject *m;
	m = Py_InitModule3("hellowsgi", hellowsgi_methods, NULL);
	if (m == NULL){
		return;
    }
}

C/APIで重要な点はコンテナ系にオブジェクトを突っ込むと参照カウントがあがるという点です。
コンテナがGCで回収された際に中身もろとも消えて欲しいのでコンテナに入れたあと参照カウント下げています。

さてhellowsgiモジュールができました。
走らせてみましょう。

server.py
import hellowsgi
from wsgiref.simple_server import make_server

httpd = make_server('', 8000, hellowsgi.hello)
httpd.serve_forever()

greenletを使う場合にはPostgreSQLを使う方がいいという話

こんにちわ、Python界の便所虫ことmopemopeです。

前から書こうとしていた話を書いておきます。

最近、eventlet、geventが市民権を得てきましたね。
特にgunicorn経由で使用しはじめてる人も多いんじゃないかなと思います。

まあこれらは NonBlocking IO + 協調スレッドなわけなんですが、全部をうまく
非同期で処理できるわけではありません。
サードパーティのライブラリがC拡張であると割り込めないのでどうしようもないのです。

サードパーティのライブラリで一番使っているのはそうです、DBドライバです。
肝心のDBドライバがブロックしちゃうともったいないわけです。

これって問題だなーと思ったpsycopg2の人たちは拡張できる仕組みを入れました。
(2.2以降)

eventletを例に見てみましょう。

psyco_eventlet.py
import psycopg2
from psycopg2 import extensions

from eventlet.hubs import trampoline

def make_psycopg_green():
    """Configure Psycopg to be used with eventlet in non-blocking way."""
    if not hasattr(extensions, 'set_wait_callback'):
        raise ImportError(
            "support for coroutines not available in this Psycopg version (%s)"
            % psycopg2.__version__)

    extensions.set_wait_callback(eventlet_wait_callback)

def eventlet_wait_callback(conn, timeout=-1):
    """A wait callback useful to allow eventlet to work with Psycopg."""
    while 1:
        state = conn.poll()
        if state == extensions.POLL_OK:
            break
        elif state == extensions.POLL_READ:
            trampoline(conn.fileno(), read=True)
        elif state == extensions.POLL_WRITE:
            trampoline(conn.fileno(), write=True)
        else:
            raise psycopg2.OperationalError(
                "Bad result from poll: %r" % state)

とまあこれだけです。
読み書きできる状態になるまで待ち合わせるcallbackを設定します。
eventletでは読み書きできる状態になるとスレッドを再開されるtrampolineがあるのでそれを呼び出します。
これでクソ重いクエリを待続ける必要はなくなりましたね。

実際使用した例

import eventlet
eventlet.monkey_patch()

import psyco_eventlet
psyco_eventlet.make_psycopg_green()

import urllib2  # green

import psycopg2

import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
logger = logging.getLogger()
logger.info("testing psycopg2 with eventlet")

conn = psycopg2.connect("dbname=xxxxx")

def download(num, secs):
    url = "http://localhost:8000/%d/" % secs
    for i in range(num):
        logger.info("download %d start", i)
        data = urllib2.urlopen(url).read()
        logger.info("download %d end", i)

def fetch(num, secs):
    cur = conn.cursor()
    for i in range(num):
        logger.info("query %d start", i)
        cur.execute("select pg_sleep(%s)", (secs,))
        logger.info("query %d end", i)

logger.info("making jobs")
pool = eventlet.GreenPool()
pool.spawn(download, 2, 3),
pool.spawn(fetch, 3, 2),

logger.info("join begin")
pool.waitall()
logger.info("join end")

冒頭のパッチ以外は全く通常のpythonコードですね。

greenletを使う場合にはPostgreSQLを使った方がいいよという話でした。
ちなみにmeinheld 0.4からはeventletなどと同様、socketにmonkey_patchを当てれるようになりますよ!