Cythonのどツボり方シリーズ(1)

AttributeError: ‘module’ object has no attribute ‘Xyz’

or

AttributeError: ‘pkg.abc.Xyz’ object has no attribute ‘x’

cythonは、チュートリアルは優秀で、すぐに「入れる」んだけれど、リファレンスはあまり良い出来ではなく、ハマりネタは多い。今回は、比較的最初の頃にどツボる2つを紹介。なお、0.22時点での情報です。

pxd、pyx は以下とする:

abc.pxd
1 cdef class Xyz(object):
2     cpdef public double x
3     cpdef public double y
4     cpdef public str name
abc.pyx
1 cimport abc
2 
3 cdef class Xyz(object):
4     def __cinit__(self):
5         self.x = 3.14
6         self.y = 0.23
7         self.name = "NaMAE"

以下は、失敗するパターンの setup.py と構成:

setup.py
 1 from distutils.core import setup
 2 from distutils.extension import Extension
 3 from Cython.Distutils import build_ext
 4 
 5 setup(
 6     cmdclass={'build_ext': build_ext},
 7     ext_modules=[
 8         Extension("abc",
 9                   [
10                 "abc.pxd",
11                 "abc.pyx",
12                 ]),
13         ]
14 )
1 me@host: work$ ls -l
2 total 3
3 -rw-r--r-- 1 hhsprings Administrators 106 Apr 10 03:35 abc.pxd
4 -rw-r--r-- 1 hhsprings Administrators 141 Apr 10 03:34 abc.pyx
5 -rw-r--r-- 1 hhsprings Administrators 365 Apr 10 04:26 setup.py

これ、ビルドは成功し、モジュールは出来上がってしまうのが問題。

1 me@host: work$ python setup.py build_ext --inplace

すれば Windows なら pyd が、Unix 系なら so が「正しく」作られる(ようにみえる)。が、これは決して動作しない。

1 me@host: work$ python -c 'import abc ; z = abc.Xyz()'
2 Traceback (most recent call last):
3   File "<string>", line 1, in <module>
4 AttributeError: 'module' object has no attribute 'Xyz'
5 me@host: work$ python -c 'import abc ; print(dir(abc))'
6 ['ABCMeta', 'WeakSet', '_C', '_InstanceType', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'abstractmethod', 'abstractproperty', 'types']

ABCMetaはモジュール abc とは関係ない。これは「AbstractBaseMeta」と思う。

次に失敗するのが、以下だ:

setup.py
 1 from distutils.core import setup
 2 from distutils.extension import Extension
 3 from Cython.Distutils import build_ext
 4 
 5 setup(
 6     cmdclass={'build_ext': build_ext},
 7     packages=['pkg'],
 8     package_dir = {'pkg': 'pkg'},
 9     ext_modules=[
10         Extension("pkg.abc",
11                   [
12                 "pkg/abc.pxd",
13                 "pkg/abc.pyx",
14                 ]),
15         ]
16 )
1 me@host: work$ find . -type f -ls
2 46280596    1 -rw-r--r--   1 hhsprings Administrators      106 Apr  03:35 ./pkg/abc.pxd
3 10695898    1 -rw-r--r--   1 hhsprings Administrators      141 Apr  03:34 ./pkg/abc.pyx
4 14090988    1 -rw-r--r--   1 hhsprings Administrators      394 Apr  03:39 ./setup.py

「__init__.pyがない」ことには、Python を知っている人なら気付くであろう。ただ、問題は「そこだけれどもそこではない」のだ。

問題は、「__init__.pyはあとで書けばいい」と思って

1 me@host: work$ python setup.py build_ext --inplace

してしまいがちなことにある。これでまたWindows なら pyd が、Unix 系なら so が「正しく」作られる(ようにみえる)。まずはお約束で、

1 me@host: work$ python -c 'import pkg.abc ; z = pkg.abc.Xyz()'
2 Traceback (most recent call last):
3   File "<string>", line 1, in <module>
4 ImportError: No module named pkg.abc

へへ、知ってるんだぜ、__init__.pyを置けばいいのさ:

1 me@host: work$ touch pkg/__init__.py
2 me@host: work$ python -c 'import pkg.abc ; z = pkg.abc.Xyz()'
3 Traceback (most recent call last):
4   File "<string>", line 1, in <module>
5   File "pkg/abc.pyx", line 5, in pkg.abc.Xyz.__cinit__ (pkg\abc.c:609)
6     self.x = 3.14
7 AttributeError: 'pkg.abc.Xyz' object has no attribute 'x'

んんん??? と、ここから迷走が始まる。なんだ、何が起こってるんだ。

答えは簡単で、cython は、__init__.py の有無をコンパイル時にみている。つまり有無でビルド結果が変わる。(というよりは、cython のビルダが「真面目に」パッケージ検索・モジュール検索の python 標準に従っているので、多分当然の結果なのだと思う。けれどこういったことはドキュメントに一言欲しい。)

つまり、ビルドする前に、__init__.py が存在していなければならないのである。

ちなみにアタシはこの2つで同時にハマり、3時間近く悩んだ。pxd、pyx の書き方がマズいんじゃないだろうか、と思うじゃないか、フツー。


※cythonのどツボり方シリーズ(2)の予定は未定です、あしからず。