F2PY用户指南和参考手册 > 三种包装方式-入门
使用F2PY将Fortran或C函数包装到Python中包含以下步骤:
创建所谓的签名文件,其中包含对Fortran或C函数的包装的描述,也称为函数的签名。对于Fortran例程,F2PY可以通过扫描Fortran源代码并捕获创建包装函数所需的所有相关信息来创建初始签名文件。
可选地,可以编辑F2PY创建的签名文件以优化包装器功能,使它们“更智能”,更“ Pythonic”。
F2PY读取签名文件并编写包含Fortran / C / Python绑定的Python C / API模块。
F2PY编译所有源代码并构建包含包装程序的扩展模块。在构建扩展模块时,F2PY使用
numpy_distutils
支持许多Fortran 77/90/95编译器,包括Gnu,Intel,Sun Fortre,SGI MIPSpro,Absoft,NAG,Compaq等编译器。
根据特定的情况,这些步骤可以仅通过一个命令执行,也可以逐步执行,某些步骤可以省略或与其他步骤组合。
下面,我将描述三种使用F2PY的典型方法。以下示例Fortran 77代码将用于说明:
C FILE: FIB1.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB1.F
将Fortran子例程包装FIB
到Python 的最快方法是运行
python -m numpy.f2py -c fib1.f -m fib1
此命令将扩展模块(请参见标志)构建(请参见-c
标志,不带参数执行以查看命令行选项的说明)到当前目录。现在,在Python中,可以通过以下方式访问Fortran子例程:python -m numpy.f2py
fib1.so
-m
FIB
fib1.fib
>>> import numpy
>>> import fib1
>>> print fib1.fib.__doc__
fib - Function signature:
fib(a,[n])
Required arguments:
a : input rank-1 array('d') with bounds (n)
Optional arguments:
n := len(a) input int
>>> a = numpy.zeros(8,'d')
>>> fib1.fib(a)
>>> print a
[ 0. 1. 1. 2. 3. 5. 8. 13.]
注意
请注意,F2PY发现第二个参数n
是第一个数组参数的维a
。由于默认情况下所有参数都是仅输入参数,因此F2PY得出结论,n
默认值可以是可选的len(a)
。
一个可以为可选使用不同的值n
:
>>> a1 = numpy.zeros(8,'d')
>>> fib1.fib(a1,6)
>>> print a1
[ 0. 1. 1. 2. 3. 5. 0. 0.]
但是当它与输入数组不兼容时会引发异常a
:
>>> fib1.fib(a,10)
fib:n=10
Traceback (most recent call last):
File "<stdin>", line 1, in ?
fib.error: (len(a)>=n) failed for 1st keyword n
>>>
这证明了F2PY的有用功能之一,即F2PY在相关参数之间实现了基本的兼容性检查,以避免任何意外的崩溃。
当一个NumPy数组(它是Fortran连续的并且具有对应于假定的Fortran类型的dtype)用作输入数组参数时,其C指针将直接传递给Fortran。
否则,F2PY会创建输入数组的连续副本(具有正确的dtype),并将副本的C指针传递给Fortran子例程。结果,对输入数组(的副本)的任何可能更改都不会影响原始参数,如下所示:
>>> a = numpy.ones(8,'i')
>>> fib1.fib(a)
>>> print a
[1 1 1 1 1 1 1 1]
显然,这不是预期的行为。上面的示例适用的事实dtype=float
被认为是偶然的。
F2PY提供的intent(inplace)
属性会修改输入数组的属性,以便Fortran例程所做的任何更改在输入参数中也将有效。例如,如果指定(请参见下文,方法),那么上面的示例将显示为:intent(inplace) a
>>> a = numpy.ones(8,'i')
>>> fib1.fib(a)
>>> print a
[ 0. 1. 1. 2. 3. 5. 8. 13.]
但是,建议将Fortran子例程所做的更改返回给python的推荐方法是使用intent(out)
attribute。它是更有效和更清洁的解决方案。
fib1.fib
在Python中的用法与FIB
在Fortran中的用法非常相似
。但是,在Python中使用原位输出参数表示样式较差,因为Python中没有针对错误参数类型的安全机制。使用Fortran或C时,编译器自然会在编译期间发现任何类型不匹配的情况,但是在Python中,必须在运行时检查类型。因此,在Python中使用原位输出参数可能会导致难以发现错误,更不用说在实现所有必需的类型检查后,代码的可读性也会降低。
尽管演示了将Fortran例程包装到Python的方法非常简单,但是它也有一些缺点(请参见上面的注释)。这些缺点是由于以下事实造成的:F2PY无法确定一个或另一个自变量的实际意图是输入或输出自变量,还是两者兼而有之。因此,F2PY保守地假设默认情况下所有参数都是输入参数。
但是,有很多方法(见下文)如何“教” F2PY函数参数的真实意图(除其他外)。然后F2PY能够为Fortran函数生成更多的Pythonic(更显式,更易于使用且更不易出错)包装。
让我们应用将Fortran函数一个接一个地包装到Python的步骤。
首先,我们fib1.f
通过运行来创建一个签名文件
python -m numpy.f2py fib1.f -m fib2 -h fib1.pyf
签名文件保存到fib1.pyf
(请参见-h
标志),其内容如下所示。
! -*- f90 -*-
python module fib2 ! in
interface ! in :fib2
subroutine fib(a,n) ! in :fib2:fib1.f
real*8 dimension(n) :: a
integer optional,check(len(a)>=n),depend(a) :: n=len(a)
end subroutine fib
end interface
end python module fib2
! This file was auto-generated with f2py (version:2.28.198-1366).
! See http://cens.ioc.ee/projects/f2py2e/
接下来,我们将告诉F2PY该参数n
是输入参数(使用intent(in)
属性),并且结果(即a
调用Fortran函数之后的内容)FIB
应返回给Python(使用intent(out)
属性)。另外,a
应该使用输入参数给定的大小动态创建数组n
(使用depend(n)
属性指示依赖关系)。
的修改版本fib1.pyf
(保存为
fib2.pyf
)的内容如下:
! -*- f90 -*-
python module fib2
interface
subroutine fib(a,n)
real*8 dimension(n),intent(out),depend(n) :: a
integer intent(in) :: n
end subroutine fib
end interface
end python module fib2
最后,我们通过运行构建扩展模块
python -m numpy.f2py -c fib2.pyf fib1.f
在Python中:
>>> import fib2
>>> print fib2.fib.__doc__
fib - Function signature:
a = fib(n)
Required arguments:
n : input int
Return objects:
a : rank-1 array('d') with bounds (n)
>>> print fib2.fib(8)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
注意
显然,fib2.fib
现在的签名FIB
更符合Fortran子例程的意图:给定number n
,fib2.fib
则将第一个n
斐波那契数作为NumPy数组返回。此外,新的Python签名还fib2.fib
排除了我们遇到的任何惊喜fib1.fib
。
请注意,默认情况下使用single intent(out)
也意味着
intent(hide)
。具有intent(hide)
指定属性的参数将不会在包装函数的参数列表中列出。
如上所述,包装Fortran函数的“智能方式”适用于包装(例如第三方)Fortran代码,对于这些代码,不希望甚至无法修改其源代码。
但是,如果可以接受编辑Fortran代码,则在大多数情况下可以跳过中间签名文件的生成。即,可以使用所谓的F2PY指令将F2PY特定的属性直接插入到Fortran源代码中。F2PY指令定义了特殊注释行(Cf2py
例如,以开头),但Fortran编译器会忽略这些注释行,但F2PY会将其解释为普通行。
这里显示了示例Fortran代码的修改版本,另存为fib3.f
:
C FILE: FIB3.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
Cf2py intent(in) n
Cf2py intent(out) a
Cf2py depend(n) a
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB3.F
现在可以通过一个命令来构建扩展模块:
python -m numpy.f2py -c -m fib3 fib3.f
请注意,结果包装器与FIB
前面的情况一样“智能”:
>>> import fib3
>>> print fib3.fib.__doc__
fib - Function signature:
a = fib(n)
Required arguments:
n : input int
Return objects:
a : rank-1 array('d') with bounds (n)
>>> print fib3.fib(8)
[ 0. 1. 1. 2. 3. 5. 8. 13.]