Link f2py 使用 setuptools 在 python 包中生成 *.so 文件
Link f2py generated *.so file in a python package using setuptools
我希望使用安装工具将包部署到 PyPi。然而,包的核心部分实际上是用 Fortran 编写的,我正在使用 f2py 将其包装在 python 中。项目的结构基本上是这样的:
my_project
- license.txt
- README.md
- setup.py
- my_project
- init.py
- myfunc.py
- hello.so
模块 myfunc.py 导入 hello.so (import my_project.hello
),然后可以由 myfunc.py 中的函数使用。这在我的机器上完美运行。
然后我尝试了标准的 setuptools 安装:sudo python3 setup.py install
在我的 Ubuntu 上,它安装得很完美。但不幸的是,在导入时,它会抛出 ModuleNotFoundError: No module named 'hello'
.
现在,据我了解,在基于 Linux 的系统上,对于 python,共享库 *.so 存储在 /usr/lib/python3/dist-packages/
中。所以我手动将这个 hello.so
复制到那里,我得到了一个工作包!但当然这只适用于本地。我想做的是告诉 setuptools 在 python-egg 中包含 hello.so
并自动进行复制等,这样当用户使用 pip3 install my_package
时,他们就可以访问这个自动共享库。我可以看到 numpy 以某种方式实现了这一点,但即使在查看了他们的代码之后,我也无法解码他们是如何做到的。有人可以帮我弄这个吗?提前致谢。
你可以用这样的setup.py
文件来实现(简化版本,只保留构建外部模块的相关部分)
import os
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
class f2py_Extension(Extension):
def __init__(self, name, sourcedirs):
Extension.__init__(self, name, sources=[])
self.sourcedirs = [os.path.abspath(sourcedir) for sourcedir in sourcedirs]
self.dirs = sourcedirs
class f2py_Build(build_ext):
def run(self):
for ext in self.extensions:
self.build_extension(ext)
def build_extension(self, ext):
# compile
for ind,to_compile in enumerate(ext.sourcedirs):
module_loc = os.path.split(ext.dirs[ind])[0]
module_name = os.path.split(to_compile)[1].split('.')[0]
os.system('cd %s;f2py -c %s -m %s' % (module_loc,to_compile,module_name))
setup(
name="foo",
ext_modules=[f2py_Extension('fortran_external',['foo/one.F90','foo/bar/two.F90'])],
cmdclass=dict(build_ext=f2py_Build),
)
构建外部模块的必要部分是 setup(...)
中的 ext_modules
和 cmdclass
。 ext_modules
只是一个Extension实例的列表,每个Extension实例描述了一组扩展模块。在上面的 setup.py
中,我告诉 ext_modules
我想用两个源文件 foo/test.F90
和 foo/bar/two.F90
创建两个外部模块。基于ext_modules
,cmdclass
负责编译这两个模块,在我们的例子中,编译模块的命令是
'cd %s;f2py -c %s -m %s' % (module_loc,to_compile,module_name)
安装前的项目结构
├── foo
│ ├── __init__.py
│ ├── bar
│ │ └── two.F90
│ └── one.F90
└── setup.py
python setup.py install
之后的项目结构
├── build
│ └── bdist.linux-x86_64
├── dist
│ └── foo-0.0.0-py3.7-linux-x86_64.egg
├── foo
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-37.pyc
│ ├── bar
│ │ ├── two.F90
│ │ └── two.cpython-37m-x86_64-linux-gnu.so
│ ├── one.F90
│ └── one.cpython-37m-x86_64-linux-gnu.so
├── foo.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ └── top_level.txt
└── setup.py
one.F90
和two.F90
这两个源文件很简单
one.F90
module test
implicit none
contains
subroutine add(a)
implicit none
integer :: a
integer :: b
b = a + 1
print *, 'one',b
end subroutine add
end module test
two.F90
module test
implicit none
contains
subroutine add(a)
implicit none
integer :: a
integer :: b
b = a + 2
print *, 'two',b
end subroutine add
end module test
我安装包后,可以成功运行
>>> from foo.bar.two import test
>>> test.add(5)
two 7
和
>>> from foo.one import test
>>> test.add(5)
one 6
这是一种基于 F2PY's documentation 的方法(那里的示例包括构建多个 F2PY 模块,每个模块有多个源文件),利用 numpy.distutils
,支持 Fortran 源文件。
具有多个 F2PY 扩展模块的最小示例的结构基于 src
directory layout。它不是necessary/required,但优点是测试例程不能运行,除非包已经安装成功。
源布局
my_project
|
+-- src
| |
| +-- my_project
| |
| +-- __init__.py
| +-- mod1.py
| +-- funcs_m.f90
| +-- two
| |
| +-- pluss2.f90
| +-- times2.f90
|
+-- test_my_project.py
+-- setup.py
- setup.py
from setuptools import find_packages
from numpy.distutils.core import setup, Extension
ext1 = Extension(name='my_project.modf90',
sources=['src/my_project/funcs_m.f90'],
f2py_options=['--quiet'],
)
ext2 = Extension(name='my_project.oldf90',
sources=['src/my_project/two/plus2.f90', 'src/my_project/two/times2.f90'],
f2py_options=['--quiet'],
)
setup(name="my_project",
version="0.0.1",
package_dir={"": "src"},
packages=find_packages(where="src"),
ext_modules=[ext1, ext2])
- __init__.py
__init__.py
文件为空。 (例如,如果需要,可以在此处导入 F2PY 模块)
- mod1.py
def add(a, b):
""" add inputs a and b, and return """
return a + b
- funcs_m.f90
module funcs_m
implicit none
contains
subroutine add(a, b, c)
integer, intent(in) :: a
integer, intent(in) :: b
integer, intent(out) :: c
c = a + b
end subroutine add
end module funcs_m
- plus2.f90
subroutine plus2(x, y)
integer, intent(in) :: x
integer, intent(out) :: y
y = x + 2
end subroutine plus2
- times2.f90
subroutine times2(x, y)
integer, intent(in) :: x
integer, intent(out) :: y
y = x * 2
end subroutine times2
- test_my_project.py
import my_project.mod1
import my_project.oldf90
import my_project.modf90
print("mod1.add: 1 + 2 = ", my_project.mod1.add(1, 2))
print("modf90.funcs_m.add: 1 + 2 = ", my_project.modf90.funcs_m.add(1, 2))
x = 1
x = my_project.oldf90.plus2(x)
print("oldf90.plus2: 1 + 2 = ", x)
x = my_project.oldf90.times2(x)
print("oldf90.plus2: 3 * 2 = ", x)
正在安装
现在,可以使用 pip
安装软件包。与 setup.py install
相比,使用 pip
(包括易于升级或卸载)有几个 advantages(但这仍然可以用于构建分发包!)。来自包含 setup.py
的目录:
> python -m pip install .
测试
然后,测试刚刚安装的包
> python test_my_project.py
mod1.add: 1 + 2 = 3
modf90.funcs_m.add: 1 + 2 = 3
oldf90.plus2: 1 + 2 = 3
oldf90.plus2: 3 * 2 = 6
此设置已在 Windows 10(使用 ifort)、Ubuntu 18.04(使用 gfortran)和 MacOS High Sierra(使用 gfortran)上成功测试,全部使用 Python 3.6.3.
我希望使用安装工具将包部署到 PyPi。然而,包的核心部分实际上是用 Fortran 编写的,我正在使用 f2py 将其包装在 python 中。项目的结构基本上是这样的:
my_project
- license.txt
- README.md
- setup.py
- my_project
- init.py
- myfunc.py
- hello.so
模块 myfunc.py 导入 hello.so (import my_project.hello
),然后可以由 myfunc.py 中的函数使用。这在我的机器上完美运行。
然后我尝试了标准的 setuptools 安装:sudo python3 setup.py install
在我的 Ubuntu 上,它安装得很完美。但不幸的是,在导入时,它会抛出 ModuleNotFoundError: No module named 'hello'
.
现在,据我了解,在基于 Linux 的系统上,对于 python,共享库 *.so 存储在 /usr/lib/python3/dist-packages/
中。所以我手动将这个 hello.so
复制到那里,我得到了一个工作包!但当然这只适用于本地。我想做的是告诉 setuptools 在 python-egg 中包含 hello.so
并自动进行复制等,这样当用户使用 pip3 install my_package
时,他们就可以访问这个自动共享库。我可以看到 numpy 以某种方式实现了这一点,但即使在查看了他们的代码之后,我也无法解码他们是如何做到的。有人可以帮我弄这个吗?提前致谢。
你可以用这样的setup.py
文件来实现(简化版本,只保留构建外部模块的相关部分)
import os
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
class f2py_Extension(Extension):
def __init__(self, name, sourcedirs):
Extension.__init__(self, name, sources=[])
self.sourcedirs = [os.path.abspath(sourcedir) for sourcedir in sourcedirs]
self.dirs = sourcedirs
class f2py_Build(build_ext):
def run(self):
for ext in self.extensions:
self.build_extension(ext)
def build_extension(self, ext):
# compile
for ind,to_compile in enumerate(ext.sourcedirs):
module_loc = os.path.split(ext.dirs[ind])[0]
module_name = os.path.split(to_compile)[1].split('.')[0]
os.system('cd %s;f2py -c %s -m %s' % (module_loc,to_compile,module_name))
setup(
name="foo",
ext_modules=[f2py_Extension('fortran_external',['foo/one.F90','foo/bar/two.F90'])],
cmdclass=dict(build_ext=f2py_Build),
)
构建外部模块的必要部分是 setup(...)
中的 ext_modules
和 cmdclass
。 ext_modules
只是一个Extension实例的列表,每个Extension实例描述了一组扩展模块。在上面的 setup.py
中,我告诉 ext_modules
我想用两个源文件 foo/test.F90
和 foo/bar/two.F90
创建两个外部模块。基于ext_modules
,cmdclass
负责编译这两个模块,在我们的例子中,编译模块的命令是
'cd %s;f2py -c %s -m %s' % (module_loc,to_compile,module_name)
安装前的项目结构
├── foo
│ ├── __init__.py
│ ├── bar
│ │ └── two.F90
│ └── one.F90
└── setup.py
python setup.py install
├── build
│ └── bdist.linux-x86_64
├── dist
│ └── foo-0.0.0-py3.7-linux-x86_64.egg
├── foo
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-37.pyc
│ ├── bar
│ │ ├── two.F90
│ │ └── two.cpython-37m-x86_64-linux-gnu.so
│ ├── one.F90
│ └── one.cpython-37m-x86_64-linux-gnu.so
├── foo.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ └── top_level.txt
└── setup.py
one.F90
和two.F90
这两个源文件很简单
one.F90
module test
implicit none
contains
subroutine add(a)
implicit none
integer :: a
integer :: b
b = a + 1
print *, 'one',b
end subroutine add
end module test
two.F90
module test
implicit none
contains
subroutine add(a)
implicit none
integer :: a
integer :: b
b = a + 2
print *, 'two',b
end subroutine add
end module test
我安装包后,可以成功运行
>>> from foo.bar.two import test
>>> test.add(5)
two 7
和
>>> from foo.one import test
>>> test.add(5)
one 6
这是一种基于 F2PY's documentation 的方法(那里的示例包括构建多个 F2PY 模块,每个模块有多个源文件),利用 numpy.distutils
,支持 Fortran 源文件。
具有多个 F2PY 扩展模块的最小示例的结构基于 src
directory layout。它不是necessary/required,但优点是测试例程不能运行,除非包已经安装成功。
源布局
my_project
|
+-- src
| |
| +-- my_project
| |
| +-- __init__.py
| +-- mod1.py
| +-- funcs_m.f90
| +-- two
| |
| +-- pluss2.f90
| +-- times2.f90
|
+-- test_my_project.py
+-- setup.py
- setup.py
from setuptools import find_packages
from numpy.distutils.core import setup, Extension
ext1 = Extension(name='my_project.modf90',
sources=['src/my_project/funcs_m.f90'],
f2py_options=['--quiet'],
)
ext2 = Extension(name='my_project.oldf90',
sources=['src/my_project/two/plus2.f90', 'src/my_project/two/times2.f90'],
f2py_options=['--quiet'],
)
setup(name="my_project",
version="0.0.1",
package_dir={"": "src"},
packages=find_packages(where="src"),
ext_modules=[ext1, ext2])
- __init__.py
__init__.py
文件为空。 (例如,如果需要,可以在此处导入 F2PY 模块)
- mod1.py
def add(a, b):
""" add inputs a and b, and return """
return a + b
- funcs_m.f90
module funcs_m
implicit none
contains
subroutine add(a, b, c)
integer, intent(in) :: a
integer, intent(in) :: b
integer, intent(out) :: c
c = a + b
end subroutine add
end module funcs_m
- plus2.f90
subroutine plus2(x, y)
integer, intent(in) :: x
integer, intent(out) :: y
y = x + 2
end subroutine plus2
- times2.f90
subroutine times2(x, y)
integer, intent(in) :: x
integer, intent(out) :: y
y = x * 2
end subroutine times2
- test_my_project.py
import my_project.mod1
import my_project.oldf90
import my_project.modf90
print("mod1.add: 1 + 2 = ", my_project.mod1.add(1, 2))
print("modf90.funcs_m.add: 1 + 2 = ", my_project.modf90.funcs_m.add(1, 2))
x = 1
x = my_project.oldf90.plus2(x)
print("oldf90.plus2: 1 + 2 = ", x)
x = my_project.oldf90.times2(x)
print("oldf90.plus2: 3 * 2 = ", x)
正在安装
现在,可以使用 pip
安装软件包。与 setup.py install
相比,使用 pip
(包括易于升级或卸载)有几个 advantages(但这仍然可以用于构建分发包!)。来自包含 setup.py
的目录:
> python -m pip install .
测试
然后,测试刚刚安装的包
> python test_my_project.py
mod1.add: 1 + 2 = 3
modf90.funcs_m.add: 1 + 2 = 3
oldf90.plus2: 1 + 2 = 3
oldf90.plus2: 3 * 2 = 6
此设置已在 Windows 10(使用 ifort)、Ubuntu 18.04(使用 gfortran)和 MacOS High Sierra(使用 gfortran)上成功测试,全部使用 Python 3.6.3.