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

当我在Windows上使用setup.py build_ext

--inplace命令编译任意一个__init__.py文件时,它具有无法解析的外部符号错误(即“

LINK:错误LNK2001:无法解析的外部符号PyInit___init__”)。

当地环境:

python3.7,

Cython 0.29.14,

window10 x64,

Microsoft Visual Studio 2017,

ctest / .py

# cython: language_level=3

print('__init__')

setup.py

from distutils.core import setup

from Cython.Build import cythonize

def compile_code(name, filename):

setup(

name=name,

ext_modules=cythonize(filename),

)

if __name__ == '__main__':

compile_code('a', 'ctest/__init__.py')

终端打印的信息:

Compiling ctest/__init__.py because it changed.

[1/1] Cythonizing ctest/__init__.py

running build_ext

building 'ctest.__init__' extension

creating build

creating build\temp.win-amd64-3.7

creating build\temp.win-amd64-3.7\Release

creating build\temp.win-amd64-3.7\Release\ctest

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -Id:\py37\include -Id:\py37\incl

ude "-IC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" "-IC:\Pro

gram Files (x86)\Windows Kits\10\include\10.0.18362.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\shared" "-IC:\Program Files (x86)\Windows Kits\10\includ

e\10.0.18362.0\um" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\winrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\cppwinrt" /Tcctest/__init__

.c /Fobuild\temp.win-amd64-3.7\Release\ctest/__init__.obj

__init__.c

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTU

AC:NO /LIBPATH:d:\py37\Libs /LIBPATH:D:\ENVS\cpytrantest\libs /LIBPATH:D:\ENVS\cpytrantest\PCbuild\amd64 "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC

\Tools\MSVC\14.16.27023\lib\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\lib\um\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\ucrt\x6

4" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\um\x64" /EXPORT:PyInit___init__ build\temp.win-amd64-3.7\Release\ctest/__init__.obj /OUT:C:\Users\76923\Deskto

p\cpythonrecord\ctest\__init__.cp37-win_amd64.pyd /IMPLIB:build\temp.win-amd64-3.7\Release\ctest\__init__.cp37-win_amd64.lib

LINK : error LNK2001: An unresolvable external symbol PyInit___init__

build\temp.win-amd64-3.7\Release\ctest\__init__.cp37-win_amd64.lib : fatal error LNK1120: An external command that cannot be parsed

error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Tools\\MSVC\\14.16.27023\\bin\\HostX86\\x64\\link.exe' failed with exit status 1120

回答:

也许此行为可能被视为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__.soPyInit_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_ext

def 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警告的原因:

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

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

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

...

from distutils.command.build_ext import build_ext

def 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!

以上是 如何在Windows上使用cython编译__init__.py文件 的全部内容, 来源链接: utcz.com/qa/432117.html

回到顶部