Я бы, вероятно, согласился с решением @hoefling: небольшой недостаток - дополнительная неявная зависимость в порядке выполнения (include_dirs
должна идти после setup_requires
), вероятно, является чисто академической проблемой.
Тем не менее, я бы добавил еще одно хакерское решение. Но сначала давайте разберемся, почему другие решения терпят неудачу.
Первый вопрос, когда numpy
нужен? Это необходимо во время установки (т. Е. Когда вызывается build_ext-funcionality) и при установке, когда используется модуль. Это означает, что numpy
должно быть в setup_requires
и в install_requires
.
Давайте пока посмотрим на неудачные попытки:
pybind11-трик
@ chrisb's "pybind11" -трюк, который можно найти здесь : с помощью косвенного обращения вы задерживаете вызов на 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
-объект в os.path.join(...,...)
, который проверяет, является ли аргумент действительно строкой, что, очевидно, не является.
Это можно исправить, унаследовав от str
, но приведенное выше показывает опасности подхода в долгосрочной перспективе - он не использует разработанную механику, хрупок и может легко потерпеть неудачу в будущем.
Что выглядит следующим образом:
...
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
-файлы были признаны в первую очередь? Ответ эта часть из 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
пытается использовать build_ext Cython, если это возможно, и поскольку импорт модуля задерживается до вызова build_ext
, он находит Cython.
Ситуация меняется, когда setuptools.command.build_ext
импортируется в начале setup.py
- Cython еще не присутствует и используется откат без функциональности Cython.
смешивание трюка Pybind11 и классического решения
Итак, давайте добавим косвенное указание, чтобы нам не пришлось импортировать setuptools.command.build_ext
непосредственно в начале setup.py
:
....
# 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},
...
)
Итак, в конце: выберите свой яд - кажется, есть только хакерские способы сделать это!