如何在Windows上使用cython编译__init__.py文件

如何在Windows上使用cython编译__init__.py文件,第1张

如何在Windows上使用cython编译__init__.py文件

也许此行为可能被视为

distutils
-package中的一个小错误(如@DavidW所指出的,存在一个未解决的问题:https
://bugs.python.org/issue35893 )。但是,它也表明,cythonize /
compiling
__init__.py
并不是很流行,并且使用了一些未记录的实现细节,这些细节将来可能会改变,因此最好不要干预
__init__.py


但是如果你必须…


显式导入包时,例如

import ctest

或隐含地,例如

import ctest.something

FileFinder
会看到一个包,而不是一个模块,导入并会尝试加载
ctest/__init__.py
,而不是
ctest.py
(这很可能是不存在的):

    # Check if the module is the name of a directory (and thus a package).    if cache_module in cache:        base_path = _path_join(self.path, tail_module)        for suffix, loader_class in self._loaders: init_filename = '__init__' + suffix full_path = _path_join(base_path, init_filename) if _path_isfile(full_path):     return self._get_spec(loader_class, fullname, full_path, [base_path], target)

用于

suffix, loader_class
加载
__init__.so
__init__.py
并且
__init__.pyc

按此顺序使用(另请参见此SO-
post)。这意味着,
__init__.so
将加载而不是
__init__.py
如果我们设法创建一个。

__init__.py
执行时,该属性
__name__
是程序包的名称,即
ctest
您的情况,而不是您
__init__
想象的那样。因此,在init函数的名称,Python的解释器将在加载时,分机呼叫
__init__.so
PyInit_ctest
在你的情况(而不是
PyInit___init__
像一些人认为)。

上面说明了为什么所有这些都可以在Linux上直接使用。Windows呢?

加载程序只能使用来自so / dll的未隐藏符号。默认情况下,使用gcc构建的所有符号都是可见的,但Windows上的VisualStudio则不可见-
默认情况下,所有符号都是隐藏的(请参见此SO-post)。

但是,C扩展名的init函数必须是可见的(并且只有init函数),以便可以在加载程序的帮助下进行调用-
解决方案是

PyInit_ctest
在链接时导出此符号(即),在您情况下,是
/EXPORT:PyInit___init__
链接器的错误选项。

这个问题可以在distutils中找到,或者更精确地在

build_ext
-class中找到:

def get_export_symbols(self, ext):    """Return the list of symbols that a shared extension has to    export.  This either uses 'ext.export_symbols' or, if it's not    provided, "PyInit_" + module_name.  only relevant on Windows, where    the .pyd file (DLL) must export the module "PyInit_" function.    """    initfunc_name = "PyInit_" + ext.name.split('.')[-1]    if initfunc_name not in ext.export_symbols:        ext.export_symbols.append(initfunc_name)    return ext.export_symbols

可悲的

ext.name
__init__
在这里。

从这里开始,一种可能的解决方案很简单:覆盖

get_export_symbols
,即将以下内容添加到您的
setup.py
-file中(请阅读以获取更简单的版本):

...from distutils.command.build_ext import build_extdef get_export_symbols_fixed(self, ext):    names = ext.name.split('.')    if names[-1] != "__init__":        initfunc_name = "PyInit_" + names[-1]    else:        # take name of the package if it is an __init__-file        initfunc_name = "PyInit_" + names[-2]    if initfunc_name not in ext.export_symbols:        ext.export_symbols.append(initfunc_name)    return ext.export_symbols# replace wrong version with the fixed:build_ext.get_export_symbols = get_export_symbols_fixed...

python setup.py build_ext -i
现在调用应该足够了(因为
__init__.so
将被加载而不是
__init__.py
)。


但是,正如@DawidW指出的,Cython使用macro

PyMODINIT_FUNC
,其定义为

#define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*

Py_EXPORTED_SYMBOL
被标记为出口在Windows可见/:

#define Py_EXPORTED_SYMBOL __declspec(dllexport)

因此,无需在命令行上将符号标记为可见。更糟糕的是,这是发出LNK4197警告的原因:

init 。obj:警告LNK4197:多次指定导出’PyInit_ctest’;使用第一规范

作为

PyInit_test
被标记为
__declspec(dllexport)
,并通过选项导出
/EXPORT:
在同一时间。

/EXPORT:
-option将被distutils跳过,如果
export_symbols
为空,我们甚至可以使用更简单的版本
command.build_ext

...from distutils.command.build_ext import build_extdef get_export_symbols_fixed(self, ext):    pass  # return [] also does the job!# replace wrong version with the fixed:build_ext.get_export_symbols = get_export_symbols_fixed...

这甚至比第一个版本更好,因为它还修复了警告LNK4197!



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5011083.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-15
下一篇 2022-11-14

发表评论

登录后才能评论

评论列表(0条)

保存