具有__init__.pyx的Cython包:可能吗?

32
可以使用 __init__.pyx(编译为__init__.so)创建Python 2.7包吗?如果可以,如何操作?我尝试了一些方法但都没有成功。以下是我尝试过的方法:
  • setup.py:

    #!/usr/bin/env python
    
    from distutils.core import setup
    from distutils.extension import Extension
    from Cython.Distutils import build_ext
    
    foo = Extension(name='foo.__init__', sources=['foo/__init__.pyx'])
    bar = Extension(name='foo.bar', sources=['foo/bar.pyx'])
    
    setup(name='foo',
          packages = ['foo'],
          cmdclass={'build_ext':build_ext},
          ext_modules = [foo, bar])
    
  • foo/__init__.pyx:

    import foo.bar
    
    cpdef hello_world():
        print "hello world"
        foo.bar.blah()
    
  • foo/bar.pyx:

    cpdef blah():
        print "blah"
    
上述具有以下行为:
$ python -c 'import foo; foo.hello_world()'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named foo

我看到了Python问题#15576,它被此Hg提交修复。查看Python Hg存储库的Git镜像中的等效Git提交, 我发现该提交可以从Python v2.7.5标签(以及所有后续的v2.7.x版本)访问。是否存在回归?

只是出于好奇:你为什么要那样做? - Dschoni
2个回答

16
根据这篇非常老的邮件列表帖子,如果您还有一个__init__.py文件(__init__.py文件未被使用,但似乎对于将目录视为模块以及加载__init__.so文件是必要的),那么它就可以工作。
如果我添加__init__.py
# an exception just to confirm that the .so file is loaded instead of the .py file
raise ImportError("__init__.py loaded when __init__.so should have been loaded")

那么你的示例在Linux Python 2.7.3上可以正常工作:

$ python -c 'import foo; foo.hello_world()'
hello world
blah

这似乎是一个有缺陷的边角案例,所以可能不推荐使用。请注意,在Windows上这对我来说似乎无法正常工作。
ImportError: DLL load failed: %1 is not a valid Win32 application.

附加说明(为了更好的理解):

这种行为似乎没有明确记录。在大约 Python 1.5 时代的包的原始描述中,他们说:

没有 __init__.py,目录不会被识别为一个包

还有

提示:搜索顺序由函数 imp.get_suffixes() 返回的后缀列表决定。通常后缀按以下顺序搜索:".so"、"module.so"、".py"、".pyc"。目录在此列表中并没有明确出现,但是它们在列表中的所有条目之前。

观察到的行为与此一致——__init__.py需要将目录视为包,但 .so 文件优先于 .py 文件加载——但这并不是毫无歧义的。

从Cython的角度来看,这种行为似乎被用于编译标准库(在这种情况下,__init__.py总是存在的),或者在给定的测试用例https://github.com/cython/cython/blob/master/tests/build/package_compilation.srctree中(还有其他一些示例)。在这些示例中,“srctree”文件似乎被扩展到包含__init__.py(和其他文件)的各种文件夹中,然后进行编译。只有__init__.so可能只是从未经过测试。


1
这个技巧在官方文档中提到了吗?(在那个邮件列表线程的时候显然没有,但也许事情已经有所改变) - Richard Hansen
我不这么认为。(我是偶然发现它的,过了一会儿看到了新闻组帖子后才意识到自己所做的。)如果我能找到更好的来源,我会再看一下并更新我的答案的。它似乎也适用于 __init__.pyc 文件。 - DavidW
1
改进建议:与其使用“assert False”,引发“ImportError”可能更好。或者,如果这种技巧在未来的Python版本中停止工作,可以使用“imp”模块进行一些低级别的操作。 - Richard Hansen
@RichardHansen 我已经更新了代码,现在会抛出 ImportError 异常(我同意 - 这显然更好!),并添加了一个支持此方法的文档链接。我认为这基本上是未记录的。 - DavidW

-3
尝试使用相对导入。
__init__中: from . import bar 也可以是from . import foo。有一段时间没有使用过Python 2 Cython了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接