如何在窗口上使用cython编译init.py文件

当我使用setup.py build_ext --inplace命令编译__init__.py仲裁文件时,它具有无法解决的外部符号错误。 我在这个朋友的question中看到了说明,并遇到了与我相同的问题,但是我无法访问他的答案中的链接...

本地环境:

python3.7,Cython 0.29.14,window10 x64,microsoft Visual Studio 2017,

ctest / __ init __。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
GFHJTYHTR 回答:如何在窗口上使用cython编译init.py文件

也许这种行为可能被视为distutils-package中的一个小错误。但是,它也表明,对__init__.py进行cythonize / compiling并不是很流行,它使用了一些未记录的实现细节,这些细节将来可能会改变,因此最好不要干预__init__.py

但是,如果您必须...


例如明确导入软件包时

import ctest

或隐含地,例如

import ctest.something

FileFinder将看到已导入软件包而非模块,并且will try to load 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__.so。因此,Python解释器在加载扩展名PyInit_ctest时将调用的初始化函数的名称在您的情况下为PyInit___init__(而不是人们可能想到的PyInit_ctest)。

以上解释了为什么所有这些都可以直接在Linux上运行。 Windows呢?

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

但是,C扩展名的init函数必须是可见的(并且只有init函数),以便可以在加载程序的帮助下进行调用-解决方案是导出该符号(即/EXPORT:PyInit___init__ ),则在您的情况下,链接器的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 -选项是错误的。

该问题可以在distutils中找到,或更精确的在build_ext-class中:

ext.name

可惜,__init__里面有get_export_symbols

从这里开始,一种可能的解决方案很简单:覆盖setup.py,即将以下内容添加到您的... 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而不是#define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* )。


但是,正如@DawidW所指出的,Cython使用了宏PyMODINIT_FUNC,它被定义为

#define Py_EXPORTED_SYMBOL __declspec(dllexport)

在Windows上将Py_EXPORTED_SYMBOL标记为可见/导出:

PyInit_test

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

  

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

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

export_symbols-选项将为be skipped by distutils,如果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 ... 版本:

    interface IData{
        id: number;
        name:string;
    }

    let userTestStatus:Record<string,IData> = {
        "0": { "id": 0,"name": "Available" },"1": { "id": 1,"name": "Ready" },"2": { "id": 2,"name": "Started" }
    };

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

,

这是一个非常暂定的答案,因为我没有在Windows上进行测试的简便方法,因此,如果出现错误,请告诉我,然后将其删除。

您可以尝试运行(在推荐行上):

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 /MANIFESTUAC: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\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\um\x64" /EXPORT:PyInit_ctest build\temp.win-amd64-3.7\Release\ctest/__init__.obj /OUT:C:\Users\76923\Desktop\cpythonrecord\ctest\__init__.cp37-win_amd64.pyd /IMPLIB:build\temp.win-md64-3.7\Release\ctest\__init__.cp37-win_amd64.lib

我所要做的只是使用distutils生成的编译命令,并将/EXPORT:PyInit___init__替换为/EXPORT:PyInit_ctest/EXPORT是Windows特定的编译器选项,在Linux上不会添加。看起来distutils或Cython都将名称PyInit___init__传递给了MSVC,但是如果我查看实际生成的C文件,则该名称似乎是PyInit_ctest,因此未定义符号。

如果该解决方法(独立于distutils进行编译)有效,则您应将这些错误与这些详细信息报告给distutils或Cython错误跟踪器(可能是Cython),并希望可以将其修复。

本文链接:https://www.f2er.com/3125711.html

大家都在问