Boost.Python で static property を定義したクラスを含むモジュールをロードしようとするとエラーが出る件

この問題は Python 2.6.3 以降で発生する。たとえば、次のようなモジュールを作成し、

#include <boost/python.hpp>
#include <string>

class MyClass
{
public:
    static std::string get_foo()
    {
        return "test";
    }
};

BOOST_PYTHON_MODULE(_test)
{
    using namespace boost::python;

    class_<MyClass>("myclass")
        .add_static_property("foo", &MyClass::get_foo);
}

これを、

gcc -I/usr/include/python2.6 -fPIC -DPIC -shared -o _test.so -lboost_python-mt _test.cpp

などとしてコンパイルし、import を試みると、

Python 2.6.4 (r264:75706, Nov  2 2009, 14:44:17) 
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import _test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Boost.Python.StaticProperty' object attribute '__doc__' is read-only

というようなエラーが出てしまうのである。マイナーバージョンの変更でこのような事態が発生するのは稀なので面食らってしまうが、原因は Issue 5890 を解消しようとして行われた修正が不十分だからだ。

この問題は Boost.Python に限ったものではなく、次のように、C で property を基底クラスとするクラスを定義したときに、__doc__ が read-only (≒tp_flags に Py_TPFLAGS_HAVE_CLASS がないか、もしくは tp_dictoffset が NULL) になっていると同様に発生する。

#include <Python.h>

static PyObject *my_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
    Py_INCREF(Py_None);
    return Py_None;
}

static int my_descr_set(PyObject *self, PyObject *obj, PyObject *type)
{
    return 0;
}

static PyTypeObject MyExtendedProperty_Type = {
	PyObject_HEAD_INIT(&PyType_Type)
	0,                  /* ob_size */
	"extended_property",				/* tp_name */
	0,                  /* tp_basicsize */
	0,					/* tp_itemsize */
	/* methods */
	0,			 		/* tp_dealloc */
	0,					/* tp_print */
	0,					/* tp_getattr */
	0,					/* tp_setattr */
	0,					/* tp_compare */
	0,					/* tp_repr */
	0,					/* tp_as_number */
	0,					/* tp_as_sequence */
	0,		       			/* tp_as_mapping */
	0,					/* tp_hash */
	0,					/* tp_call */
	0,					/* tp_str */
	0,		/* tp_getattro */
	0,					/* tp_setattro */
	0,					/* tp_as_buffer */
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_GC | Py_TPFLAGS_BASETYPE,		/* tp_flags */
 	0,				/* tp_doc */
	0,			/* tp_traverse */
 	0,					/* tp_clear */
	0,					/* tp_richcompare */
	0,					/* tp_weaklistoffset */
	0,					/* tp_iter */
	0,					/* tp_iternext */
	0,			/* tp_methods */
	0,			/* tp_members */
	0,					/* tp_getset */
	&PyProperty_Type,					/* tp_base */
	0,					/* tp_dict */
	my_descr_get,			/* tp_descr_get */
	my_descr_set,			/* tp_descr_set */
	0,					/* tp_dictoffset */
	0,				/* tp_init */
	PyType_GenericAlloc,			/* tp_alloc */
	PyType_GenericNew,			/* tp_new */
	PyObject_GC_Del,               		/* tp_free */
};

void init_test()
{
	PyObject *mod = Py_InitModule("_issue5890", NULL);
	PyType_Ready(&MyExtendedProperty_Type);
	PyModule_AddObject(mod, "extended_property", (PyObject *)&MyExtendedProperty_Type);
}