将 numpy.get_include() 参数添加到没有预装 numpy 的 setuptools
Add numpy.get_include() argument to setuptools without preinstalled numpy
我目前正在开发一个使用 cython
和 numpy
的 python 包,我希望可以使用 pip install
命令从干净的 python 安装。所有依赖项都应该自动安装。我正在使用 setuptools
和以下 setup.py
:
import setuptools
my_c_lib_ext = setuptools.Extension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setuptools.setup(
name="my_lib",
version="0.0.1",
author="Me",
author_email="me@myself.com",
description="Some python library",
packages=["my_lib"],
ext_modules=[my_c_lib_ext],
setup_requires=["cython >= 0.29"],
install_requires=["numpy >= 1.15"],
classifiers=[
"Programming Language :: Python :: 3",
"Operating System :: OS Independent"
]
)
到目前为止效果很好。 pip install
命令为构建下载 cython
,并且能够构建我的包并将其与 numpy
.
一起安装
现在我想提高我的 cython
代码的性能,这导致我的 setup.py
发生了一些变化。我需要将 include_dirs=[numpy.get_include()]
添加到 setuptools.Extension(...)
或 setuptools.setup(...)
的调用中,这意味着我还需要 import numpy
。 (请参阅 http://docs.cython.org/en/latest/src/tutorial/numpy.html and Make distutils look for numpy header files in the correct place 了解有理数。)
这很糟糕。现在用户无法从干净的环境中调用 pip install
,因为 import numpy
会失败。在安装我的库之前,用户需要 pip install numpy
。即使我将 "numpy >= 1.15"
从 install_requires
移动到 setup_requires
安装也会失败,因为 import numpy
被较早评估。
有没有办法在安装的后期评估 include_dirs
,例如,在解决了 setup_requires
或 install_requires
的依赖关系之后?我真的很喜欢自动解决所有依赖关系,我不希望用户键入多个 pip install
命令。
以下代码片段有效,但未得到官方支持,因为它使用了未记录(且私有)的方法:
class NumpyExtension(setuptools.Extension):
# setuptools calls this function after installing dependencies
def _convert_pyx_sources_to_lang(self):
import numpy
self.include_dirs.append(numpy.get_include())
super()._convert_pyx_sources_to_lang()
my_c_lib_ext = NumpyExtension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
文章 How to Bootstrap numpy installation in setup.py 建议使用带有自定义 build_ext
class 的 cmdclass
。不幸的是,这破坏了 cython
扩展的构建,因为 cython
还自定义了 build_ext
.
一个(hacky)建议是使用 extension.include_dirs
首先在 build_ext
中请求的事实,它在安装依赖项下载后调用。
class MyExt(setuptools.Extension):
def __init__(self, *args, **kwargs):
self.__include_dirs = []
super().__init__(*args, **kwargs)
@property
def include_dirs(self):
import numpy
return self.__include_dirs + [numpy.get_include()]
@include_dirs.setter
def include_dirs(self, dirs):
self.__include_dirs = dirs
my_c_lib_ext = MyExt(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setup(
...,
setup_requires=['cython', 'numpy'],
)
更新
另一个(更少,但我想仍然很老套)解决方案将覆盖 build
而不是 build_ext
,因为我们知道 build_ext
是 [=14= 的子命令] 并将始终在安装时由 build
调用。这样,我们就不必触摸 build_ext
并将其留给 Cython。这在直接调用 build_ext
时也有效(例如,通过 python setup.py build_ext
在开发时就地重建扩展),因为 build_ext
ensures all options of build
are initialized, and by coincidence, Command.set_undefined_options
first ensures the command has finalized (我知道,distutils
是一团糟)。
当然,现在我们误用了 build
- 它运行属于 build_ext
终结的代码。但是,我仍然可能会使用此解决方案而不是第一个解决方案,以确保正确记录相关代码段。
import setuptools
from distutils.command.build import build as build_orig
class build(build_orig):
def finalize_options(self):
super().finalize_options()
# I stole this line from ead's answer:
__builtins__.__NUMPY_SETUP__ = False
import numpy
# or just modify my_c_lib_ext directly here, ext_modules should contain a reference anyway
extension = next(m for m in self.distribution.ext_modules if m == my_c_lib_ext)
extension.include_dirs.append(numpy.get_include())
my_c_lib_ext = setuptools.Extension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setuptools.setup(
...,
ext_modules=[my_c_lib_ext],
cmdclass={'build': build},
...
)
第一个问题,什么时候需要numpy
?在设置期间(即调用 build_ext
-functionality 时)和安装中使用模块时需要它。这意味着 numpy
应该在 setup_requires
和 在 install_requires
.
有以下替代方法可以解决设置问题:
PEP 517/518-解决方案:
在 setup.py
旁边放一个 pyproject.toml
-file ,内容如下:
[build-system]
requires = ["setuptools", "wheel", "Cython>=0.29", "numpy >= 1.15"]
定义构建所需的包,然后在setup.py
文件夹中使用pip install .
安装。这种方法的缺点是 python setup.py install
不再有效,因为它是 pip
读取 pyproject.toml
。但是,我会尽可能使用这种方法。
推迟导入
这种方法比较复杂而且有点老套,但在没有 pip
.
的情况下也可以工作
首先,让我们看一下到目前为止未成功的尝试:
pybind11-trick
@chrisb 的“pybind11”技巧,可以在 here 中找到:在间接的帮助下,延迟对 import numpy
的调用,直到在设置阶段出现 numpy,即:
class get_numpy_include(object):
def __str__(self):
import numpy
return numpy.get_include()
...
my_c_lib_ext = setuptools.Extension(
...
include_dirs=[get_numpy_include()]
)
聪明!问题:它不适用于 Cython 编译器:在某个地方,Cython 将 get_numpy_include
-object 传递给 os.path.join(...,...)
,它检查参数是否真的是一个字符串,它显然不是' t.
这可以通过从 str
继承来解决,但上面显示了长 运行 方法的危险 - 它不使用设计的机制,很脆弱并且很容易将来失败。
经典build_ext
-solution
如下所示:
...
from setuptools.command.build_ext import build_ext as _build_ext
class build_ext(_build_ext):
def finalize_options(self):
_build_ext.finalize_options(self)
# Prevent numpy from thinking it is still in its setup process:
__builtins__.__NUMPY_SETUP__ = False
import numpy
self.include_dirs.append(numpy.get_include())
setupttools.setup(
...
cmdclass={'build_ext':build_ext},
...
)
然而,此解决方案也不适用于 cython 扩展,因为 pyx
文件无法识别。
真正的问题是,pyx
-文件最初是如何被识别的?答案是 this part of setuptools.command.build_ext
:
...
try:
# Attempt to use Cython for building extensions, if available
from Cython.Distutils.build_ext import build_ext as _build_ext
# Additionally, assert that the compiler module will load
# also. Ref #1229.
__import__('Cython.Compiler.Main')
except ImportError:
_build_ext = _du_build_ext
...
这意味着 setuptools
如果可能会尝试使用 Cython 的 build_ext,并且由于模块的导入延迟到 build_ext
被调用,它发现 Cython 存在。
在 setup.py
开头导入 setuptools.command.build_ext
时情况有所不同 - Cython 尚不存在,并且使用了没有 cython 功能的回退。
混合 pybind11 技巧和经典解决方案
所以我们加一个间接寻址,这样就不用在setup.py
开头直接导入setuptools.command.build_ext
了:
....
# factory function
def my_build_ext(pars):
# import delayed:
from setuptools.command.build_ext import build_ext as _build_ext#
# include_dirs adjusted:
class build_ext(_build_ext):
def finalize_options(self):
_build_ext.finalize_options(self)
# Prevent numpy from thinking it is still in its setup process:
__builtins__.__NUMPY_SETUP__ = False
import numpy
self.include_dirs.append(numpy.get_include())
#object returned:
return build_ext(pars)
...
setuptools.setup(
...
cmdclass={'build_ext' : my_build_ext},
...
)
我在 this post 中找到了一个非常简单的解决方案:
Or you can stick to https://github.com/pypa/pip/issues/5761. Here you install cython and numpy using setuptools.dist before actual setup:
from setuptools import dist
dist.Distribution().fetch_build_eggs(['Cython>=0.15.1', 'numpy>=1.10'])
很适合我!
我目前正在开发一个使用 cython
和 numpy
的 python 包,我希望可以使用 pip install
命令从干净的 python 安装。所有依赖项都应该自动安装。我正在使用 setuptools
和以下 setup.py
:
import setuptools
my_c_lib_ext = setuptools.Extension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setuptools.setup(
name="my_lib",
version="0.0.1",
author="Me",
author_email="me@myself.com",
description="Some python library",
packages=["my_lib"],
ext_modules=[my_c_lib_ext],
setup_requires=["cython >= 0.29"],
install_requires=["numpy >= 1.15"],
classifiers=[
"Programming Language :: Python :: 3",
"Operating System :: OS Independent"
]
)
到目前为止效果很好。 pip install
命令为构建下载 cython
,并且能够构建我的包并将其与 numpy
.
现在我想提高我的 cython
代码的性能,这导致我的 setup.py
发生了一些变化。我需要将 include_dirs=[numpy.get_include()]
添加到 setuptools.Extension(...)
或 setuptools.setup(...)
的调用中,这意味着我还需要 import numpy
。 (请参阅 http://docs.cython.org/en/latest/src/tutorial/numpy.html and Make distutils look for numpy header files in the correct place 了解有理数。)
这很糟糕。现在用户无法从干净的环境中调用 pip install
,因为 import numpy
会失败。在安装我的库之前,用户需要 pip install numpy
。即使我将 "numpy >= 1.15"
从 install_requires
移动到 setup_requires
安装也会失败,因为 import numpy
被较早评估。
有没有办法在安装的后期评估 include_dirs
,例如,在解决了 setup_requires
或 install_requires
的依赖关系之后?我真的很喜欢自动解决所有依赖关系,我不希望用户键入多个 pip install
命令。
以下代码片段有效,但未得到官方支持,因为它使用了未记录(且私有)的方法:
class NumpyExtension(setuptools.Extension):
# setuptools calls this function after installing dependencies
def _convert_pyx_sources_to_lang(self):
import numpy
self.include_dirs.append(numpy.get_include())
super()._convert_pyx_sources_to_lang()
my_c_lib_ext = NumpyExtension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
文章 How to Bootstrap numpy installation in setup.py 建议使用带有自定义 build_ext
class 的 cmdclass
。不幸的是,这破坏了 cython
扩展的构建,因为 cython
还自定义了 build_ext
.
一个(hacky)建议是使用 extension.include_dirs
首先在 build_ext
中请求的事实,它在安装依赖项下载后调用。
class MyExt(setuptools.Extension):
def __init__(self, *args, **kwargs):
self.__include_dirs = []
super().__init__(*args, **kwargs)
@property
def include_dirs(self):
import numpy
return self.__include_dirs + [numpy.get_include()]
@include_dirs.setter
def include_dirs(self, dirs):
self.__include_dirs = dirs
my_c_lib_ext = MyExt(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setup(
...,
setup_requires=['cython', 'numpy'],
)
更新
另一个(更少,但我想仍然很老套)解决方案将覆盖 build
而不是 build_ext
,因为我们知道 build_ext
是 [=14= 的子命令] 并将始终在安装时由 build
调用。这样,我们就不必触摸 build_ext
并将其留给 Cython。这在直接调用 build_ext
时也有效(例如,通过 python setup.py build_ext
在开发时就地重建扩展),因为 build_ext
ensures all options of build
are initialized, and by coincidence, Command.set_undefined_options
first ensures the command has finalized (我知道,distutils
是一团糟)。
当然,现在我们误用了 build
- 它运行属于 build_ext
终结的代码。但是,我仍然可能会使用此解决方案而不是第一个解决方案,以确保正确记录相关代码段。
import setuptools
from distutils.command.build import build as build_orig
class build(build_orig):
def finalize_options(self):
super().finalize_options()
# I stole this line from ead's answer:
__builtins__.__NUMPY_SETUP__ = False
import numpy
# or just modify my_c_lib_ext directly here, ext_modules should contain a reference anyway
extension = next(m for m in self.distribution.ext_modules if m == my_c_lib_ext)
extension.include_dirs.append(numpy.get_include())
my_c_lib_ext = setuptools.Extension(
name="my_c_lib",
sources=["my_c_lib/some_file.pyx"]
)
setuptools.setup(
...,
ext_modules=[my_c_lib_ext],
cmdclass={'build': build},
...
)
第一个问题,什么时候需要numpy
?在设置期间(即调用 build_ext
-functionality 时)和安装中使用模块时需要它。这意味着 numpy
应该在 setup_requires
和 在 install_requires
.
有以下替代方法可以解决设置问题:
PEP 517/518-解决方案:
在 setup.py
旁边放一个 pyproject.toml
-file ,内容如下:
[build-system]
requires = ["setuptools", "wheel", "Cython>=0.29", "numpy >= 1.15"]
定义构建所需的包,然后在setup.py
文件夹中使用pip install .
安装。这种方法的缺点是 python setup.py install
不再有效,因为它是 pip
读取 pyproject.toml
。但是,我会尽可能使用这种方法。
推迟导入
这种方法比较复杂而且有点老套,但在没有 pip
.
首先,让我们看一下到目前为止未成功的尝试:
pybind11-trick
@chrisb 的“pybind11”技巧,可以在 here 中找到:在间接的帮助下,延迟对 import numpy
的调用,直到在设置阶段出现 numpy,即:
class get_numpy_include(object):
def __str__(self):
import numpy
return numpy.get_include()
...
my_c_lib_ext = setuptools.Extension(
...
include_dirs=[get_numpy_include()]
)
聪明!问题:它不适用于 Cython 编译器:在某个地方,Cython 将 get_numpy_include
-object 传递给 os.path.join(...,...)
,它检查参数是否真的是一个字符串,它显然不是' t.
这可以通过从 str
继承来解决,但上面显示了长 运行 方法的危险 - 它不使用设计的机制,很脆弱并且很容易将来失败。
经典build_ext
-solution
如下所示:
...
from setuptools.command.build_ext import build_ext as _build_ext
class build_ext(_build_ext):
def finalize_options(self):
_build_ext.finalize_options(self)
# Prevent numpy from thinking it is still in its setup process:
__builtins__.__NUMPY_SETUP__ = False
import numpy
self.include_dirs.append(numpy.get_include())
setupttools.setup(
...
cmdclass={'build_ext':build_ext},
...
)
然而,此解决方案也不适用于 cython 扩展,因为 pyx
文件无法识别。
真正的问题是,pyx
-文件最初是如何被识别的?答案是 this part of setuptools.command.build_ext
:
...
try:
# Attempt to use Cython for building extensions, if available
from Cython.Distutils.build_ext import build_ext as _build_ext
# Additionally, assert that the compiler module will load
# also. Ref #1229.
__import__('Cython.Compiler.Main')
except ImportError:
_build_ext = _du_build_ext
...
这意味着 setuptools
如果可能会尝试使用 Cython 的 build_ext,并且由于模块的导入延迟到 build_ext
被调用,它发现 Cython 存在。
在 setup.py
开头导入 setuptools.command.build_ext
时情况有所不同 - Cython 尚不存在,并且使用了没有 cython 功能的回退。
混合 pybind11 技巧和经典解决方案
所以我们加一个间接寻址,这样就不用在setup.py
开头直接导入setuptools.command.build_ext
了:
....
# factory function
def my_build_ext(pars):
# import delayed:
from setuptools.command.build_ext import build_ext as _build_ext#
# include_dirs adjusted:
class build_ext(_build_ext):
def finalize_options(self):
_build_ext.finalize_options(self)
# Prevent numpy from thinking it is still in its setup process:
__builtins__.__NUMPY_SETUP__ = False
import numpy
self.include_dirs.append(numpy.get_include())
#object returned:
return build_ext(pars)
...
setuptools.setup(
...
cmdclass={'build_ext' : my_build_ext},
...
)
我在 this post 中找到了一个非常简单的解决方案:
Or you can stick to https://github.com/pypa/pip/issues/5761. Here you install cython and numpy using setuptools.dist before actual setup:
from setuptools import dist
dist.Distribution().fetch_build_eggs(['Cython>=0.15.1', 'numpy>=1.10'])
很适合我!