NumPy 源码解析(十)
.\numpy\numpy\f2py\symbolic.py
"""
Fortran/C symbolic expressions
References:
- J3/21-007: Draft Fortran 202x. https://j3-fortran.org/doc/year/21/21-007.pdf
Copyright 1999 -- 2011 Pearu Peterson all rights reserved.
Copyright 2011 -- present NumPy Developers.
Permission to use, modify, and distribute this software is given under the
terms of the NumPy License.
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
"""
__all__ = ['Expr']
import re
import warnings
from enum import Enum
from math import gcd
class Language(Enum):
"""
Used as Expr.tostring language argument.
"""
Python = 0
Fortran = 1
C = 2
class Op(Enum):
"""
Used as Expr op attribute.
"""
INTEGER = 10
REAL = 12
COMPLEX = 15
STRING = 20
ARRAY = 30
SYMBOL = 40
TERNARY = 100
APPLY = 200
INDEXING = 210
CONCAT = 220
RELATIONAL = 300
TERMS = 1000
FACTORS = 2000
REF = 3000
DEREF = 3001
class RelOp(Enum):
"""
Used in Op.RELATIONAL expression to specify the function part.
"""
EQ = 1
NE = 2
LT = 3
LE = 4
GT = 5
GE = 6
@classmethod
def fromstring(cls, s, language=Language.C):
"""
Convert a string representation of a relational operator to its corresponding RelOp value.
"""
if language is Language.Fortran:
return {'.eq.': RelOp.EQ, '.ne.': RelOp.NE,
'.lt.': RelOp.LT, '.le.': RelOp.LE,
'.gt.': RelOp.GT, '.ge.': RelOp.GE}[s.lower()]
return {'==': RelOp.EQ, '!=': RelOp.NE, '<': RelOp.LT,
'<=': RelOp.LE, '>': RelOp.GT, '>=': RelOp.GE}[s]
def tostring(self, language=Language.C):
"""
Convert the RelOp value to its string representation based on the specified language.
"""
if language is Language.Fortran:
return {RelOp.EQ: '.eq.', RelOp.NE: '.ne.',
RelOp.LT: '.lt.', RelOp.LE: '.le.',
RelOp.GT: '.gt.', RelOp.GE: '.ge.'}[self]
return {RelOp.EQ: '==', RelOp.NE: '!=',
RelOp.LT: '<', RelOp.LE: '<=',
RelOp.GT: '>', RelOp.GE: '>='}[self]
class ArithOp(Enum):
"""
Used in Op.APPLY expression to specify the function part.
"""
POS = 1
NEG = 2
ADD = 3
SUB = 4
MUL = 5
DIV = 6
POW = 7
class OpError(Exception):
"""
Exception raised for errors in Op operations.
"""
pass
class Precedence(Enum):
"""
Used as Expr.tostring precedence argument.
"""
ATOM = 0
POWER = 1
UNARY = 2
PRODUCT = 3
SUM = 4
LT = 6
"""
This code defines a set of classes and enums related to symbolic expressions in Fortran and C. Here's the commented version:
"""
Fortran/C symbolic expressions
References:
- J3/21-007: Draft Fortran 202x. https://j3-fortran.org/doc/year/21/21-007.pdf
Copyright 1999 -- 2011 Pearu Peterson all rights reserved.
Copyright 2011 -- present NumPy Developers.
Permission to use, modify, and distribute this software is given under the
terms of the NumPy License.
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
"""
# To analyze Fortran expressions to solve dimensions specifications,
# for instances, we implement a minimal symbolic engine for parsing
# expressions into a tree of expression instances. As a first
# instance, we care only about arithmetic expressions involving
# integers and operations like addition (+), subtraction (-),
# multiplication (*), division (Fortran / is Python //, Fortran // is
# concatenate), and exponentiation (**). In addition, .pyf files may
# contain C expressions that support here is implemented as well.
#
# TODO: support logical constants (Op.BOOLEAN)
# TODO: support logical operators (.AND., ...)
# TODO: support defined operators (.MYOP., ...)
#
__all__ = ['Expr']
import re
import warnings
from enum import Enum
from math import gcd
class Language(Enum):
"""
Enum to represent programming languages for symbolic expressions.
"""
Python = 0
Fortran = 1
C = 2
class Op(Enum):
"""
Enum to represent types of operations in symbolic expressions.
"""
INTEGER = 10
REAL = 12
COMPLEX = 15
STRING = 20
ARRAY = 30
SYMBOL = 40
TERNARY = 100
APPLY = 200
INDEXING = 210
CONCAT = 220
RELATIONAL = 300
TERMS = 1000
FACTORS = 2000
REF = 3000
DEREF = 3001
class RelOp(Enum):
"""
Enum to represent relational operators.
"""
EQ = 1
NE = 2
LT = 3
LE = 4
GT = 5
GE = 6
@classmethod
def fromstring(cls, s, language=Language.C):
"""
Convert a string representation of a relational operator to its corresponding RelOp value.
"""
if language is Language.Fortran:
return {'.eq.': RelOp.EQ, '.ne.': RelOp.NE,
'.lt.': RelOp.LT, '.le.': RelOp.LE,
'.gt.': RelOp.GT, '.ge.': RelOp.GE}[s.lower()]
return {'==': RelOp.EQ, '!=': RelOp.NE, '<': RelOp.LT,
'<=': RelOp.LE, '>': RelOp.GT, '>=': RelOp.GE}[s]
def tostring(self, language=Language.C):
"""
Convert the RelOp value to its string representation based on the specified language.
"""
if language is Language.Fortran:
return {RelOp.EQ: '.eq.', RelOp.NE: '.ne.',
RelOp.LT: '.lt.', RelOp.LE: '.le.',
RelOp.GT: '.gt.', RelOp.GE: '.ge.'}[self]
return {RelOp.EQ: '==', RelOp.NE: '!=',
RelOp.LT: '<', RelOp.LE: '<=',
RelOp.GT: '>', RelOp.GE: '>='}[self]
class ArithOp(Enum):
"""
Enum to represent arithmetic operations.
"""
POS = 1
NEG = 2
ADD = 3
SUB = 4
MUL = 5
DIV = 6
POW = 7
class OpError(Exception):
"""
Exception raised for errors in Op operations.
"""
pass
class Precedence(Enum):
"""
Enum to represent precedence levels in expression parsing.
"""
ATOM = 0
POWER = 1
UNARY = 2
PRODUCT = 3
SUM = 4
LT = 6
This Python module defines enums and classes necessary for handling symbolic expressions in Fortran and C, including various types of operations, relational operators, arithmetic operations, and precedence levels. The code also includes utility methods for converting between enum values and their string representations, specifically tailored for different programming languages.
# 定义常量 EQ,其值为 7,表示等于操作
EQ = 7
# 定义常量 LAND,其值为 11,表示逻辑与操作
LAND = 11
# 定义常量 LOR,其值为 12,表示逻辑或操作
LOR = 12
# 定义常量 TERNARY,其值为 13,表示三元操作符
TERNARY = 13
# 定义常量 ASSIGN,其值为 14,表示赋值操作
ASSIGN = 14
# 定义常量 TUPLE,其值为 15,表示元组操作
TUPLE = 15
# 定义常量 NONE,其值为 100,表示空值或未定义状态
NONE = 100
# 定义一个包含单个整数类型 int 的元组
integer_types = (int,)
# 定义一个包含整数和浮点数类型 int, float 的元组
number_types = (int, float)
def _pairs_add(d, k, v):
# 内部实用方法,用于更新字典 d 中键 k 对应的值 v
c = d.get(k)
if c is None:
d[k] = v
else:
# 如果 c 不为 None,则将其增加 v,更新到字典中
c = c + v
# 如果更新后的值 c 不为 0,则更新字典中的键 k
if c:
d[k] = c
else:
# 如果 c 为 0,则从字典中删除键 k
del d[k]
class ExprWarning(UserWarning):
# 定义一个继承自 UserWarning 的 ExprWarning 类
pass
def ewarn(message):
# 发出一个 ExprWarning 警告,警告消息为 message
warnings.warn(message, ExprWarning, stacklevel=2)
class Expr:
"""Represents a Fortran expression as a op-data pair.
Expr instances are hashable and sortable.
"""
@staticmethod
def parse(s, language=Language.C):
"""Parse a Fortran expression to a Expr.
解析 Fortran 表达式 s 到一个 Expr 对象。
"""
return fromstring(s, language=language)
# 初始化方法,接受操作符 op 和数据 data 作为参数
def __init__(self, op, data):
# 确保 op 是 Op 类的实例
assert isinstance(op, Op)
# 对不同的操作符进行不同的数据有效性检查
if op is Op.INTEGER:
# 如果操作符是 Op.INTEGER,则数据应为一个包含两个元素的元组,第一个是数值对象,第二个是种类值(默认为4)
assert isinstance(data, tuple) and len(data) == 2
assert isinstance(data[0], int)
assert isinstance(data[1], (int, str)), data
elif op is Op.REAL:
# 如果操作符是 Op.REAL,则数据应为一个包含两个元素的元组,第一个是浮点数对象,第二个是种类值(默认为4)
assert isinstance(data, tuple) and len(data) == 2
assert isinstance(data[0], float)
assert isinstance(data[1], (int, str)), data
elif op is Op.COMPLEX:
# 如果操作符是 Op.COMPLEX,则数据应为一个包含两个元素的元组,都是常量表达式
assert isinstance(data, tuple) and len(data) == 2
elif op is Op.STRING:
# 如果操作符是 Op.STRING,则数据应为一个包含两个元素的元组,第一个是带引号的字符串,第二个是种类值(默认为1)
assert isinstance(data, tuple) and len(data) == 2
# 确保第一个元素是字符串且以双引号或单引号包围,或者是一对 '@@'
assert (isinstance(data[0], str)
and data[0][::len(data[0])-1] in ('""', "''", '@@'))
assert isinstance(data[1], (int, str)), data
elif op is Op.SYMBOL:
# 如果操作符是 Op.SYMBOL,则数据可以是任意可散列对象
assert hash(data) is not None
elif op in (Op.ARRAY, Op.CONCAT):
# 如果操作符是 Op.ARRAY 或 Op.CONCAT,则数据应为一个表达式的元组
assert isinstance(data, tuple)
assert all(isinstance(item, Expr) for item in data), data
elif op in (Op.TERMS, Op.FACTORS):
# 如果操作符是 Op.TERMS 或 Op.FACTORS,则数据应为一个字典,其值为非零整数的 Python 字典
assert isinstance(data, dict)
elif op is Op.APPLY:
# 如果操作符是 Op.APPLY,则数据应为一个包含三个元素的元组,第一个元素是函数对象,第二个元素是操作数元组,第三个元素是关键字操作数字典
assert isinstance(data, tuple) and len(data) == 3
# 确保函数对象可散列
assert hash(data[0]) is not None
assert isinstance(data[1], tuple)
assert isinstance(data[2], dict)
elif op is Op.INDEXING:
# 如果操作符是 Op.INDEXING,则数据应为一个包含两个元素的元组,第一个元素是对象,第二个元素是索引
assert isinstance(data, tuple) and len(data) == 2
# 确保对象可散列
assert hash(data[0]) is not None
elif op is Op.TERNARY:
# 如果操作符是 Op.TERNARY,则数据应为一个包含三个元素的元组,分别是条件表达式、表达式1和表达式2
assert isinstance(data, tuple) and len(data) == 3
elif op in (Op.REF, Op.DEREF):
# 如果操作符是 Op.REF 或 Op.DEREF,则数据应为一个 Expr 实例
assert isinstance(data, Expr)
elif op is Op.RELATIONAL:
# 如果操作符是 Op.RELATIONAL,则数据应为一个包含三个元素的元组,分别是关系运算符、左操作数和右操作数
assert isinstance(data, tuple) and len(data) == 3
else:
# 如果操作符未知或缺少有效性检查,则引发 NotImplementedError 异常
raise NotImplementedError(
f'unknown op or missing sanity check: {op}')
# 将操作符和数据存储在对象实例的属性中
self.op = op
self.data = data
def __eq__(self, other):
# 检查是否是同一类型的表达式,并比较操作符和数据是否相等
return (isinstance(other, Expr)
and self.op is other.op
and self.data == other.data)
def __hash__(self):
# 根据表达式的操作符和数据生成哈希值
if self.op in (Op.TERMS, Op.FACTORS):
data = tuple(sorted(self.data.items()))
elif self.op is Op.APPLY:
data = self.data[:2] + tuple(sorted(self.data[2].items()))
else:
data = self.data
return hash((self.op, data))
def __lt__(self, other):
# 比较表达式的大小
if isinstance(other, Expr):
if self.op is not other.op:
return self.op.value < other.op.value
if self.op in (Op.TERMS, Op.FACTORS):
return (tuple(sorted(self.data.items()))
< tuple(sorted(other.data.items())))
if self.op is Op.APPLY:
if self.data[:2] != other.data[:2]:
return self.data[:2] < other.data[:2]
return tuple(sorted(self.data[2].items())) < tuple(
sorted(other.data[2].items()))
return self.data < other.data
return NotImplemented
def __le__(self, other):
# 检查表达式是否小于或等于另一个表达式
return self == other or self < other
def __gt__(self, other):
# 检查表达式是否大于另一个表达式
return not (self <= other)
def __ge__(self, other):
# 检查表达式是否大于或等于另一个表达式
return not (self < other)
def __repr__(self):
# 返回表达式的规范字符串表示
return f'{type(self).__name__}({self.op}, {self.data!r})'
def __str__(self):
# 返回表达式的字符串表示
return self.tostring()
def __pos__(self):
# 正号操作,返回自身
return self
def __neg__(self):
# 负号操作,返回自身乘以-1
return self * -1
def __add__(self, other):
# 加法操作
other = as_expr(other)
if isinstance(other, Expr):
if self.op is other.op:
if self.op in (Op.INTEGER, Op.REAL):
# 对整数或实数执行加法
return as_number(
self.data[0] + other.data[0],
max(self.data[1], other.data[1]))
if self.op is Op.COMPLEX:
# 对复数执行加法
r1, i1 = self.data
r2, i2 = other.data
return as_complex(r1 + r2, i1 + i2)
if self.op is Op.TERMS:
# 对项执行加法
r = Expr(self.op, dict(self.data))
for k, v in other.data.items():
_pairs_add(r.data, k, v)
return normalize(r)
if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL):
return self + as_complex(other)
elif self.op in (Op.INTEGER, Op.REAL) and other.op is Op.COMPLEX:
return as_complex(self) + other
elif self.op is Op.REAL and other.op is Op.INTEGER:
return self + as_real(other, kind=self.data[1])
elif self.op is Op.INTEGER and other.op is Op.REAL:
return as_real(self, kind=other.data[1]) + other
return as_terms(self) + as_terms(other)
return NotImplemented
# 定义特殊方法 __radd__,实现右加运算
def __radd__(self, other):
# 如果 other 是数值类型,则将其转换为表达式后执行加法运算
if isinstance(other, number_types):
return as_number(other) + self
# 如果 other 不是数值类型,则返回 NotImplemented,表示无法处理此操作
return NotImplemented
# 定义特殊方法 __sub__,实现减法运算
def __sub__(self, other):
# 返回 self 与 -other 的加法结果,实现减法运算
return self + (-other)
# 定义特殊方法 __rsub__,实现右减运算
def __rsub__(self, other):
# 如果 other 是数值类型,则将其转换为表达式后执行减法运算
if isinstance(other, number_types):
return as_number(other) - self
# 如果 other 不是数值类型,则返回 NotImplemented,表示无法处理此操作
return NotImplemented
# 定义特殊方法 __mul__,实现乘法运算
def __mul__(self, other):
# 将 other 转换为表达式类型
other = as_expr(other)
# 如果 other 是表达式对象
if isinstance(other, Expr):
# 如果两个表达式的操作符相同
if self.op is other.op:
# 处理不同操作符的乘法运算
if self.op in (Op.INTEGER, Op.REAL):
# 对整数和实数执行乘法运算
return as_number(self.data[0] * other.data[0],
max(self.data[1], other.data[1]))
elif self.op is Op.COMPLEX:
# 对复数执行乘法运算
r1, i1 = self.data
r2, i2 = other.data
return as_complex(r1 * r2 - i1 * i2, r1 * i2 + r2 * i1)
# 对因子操作进行乘法运算
if self.op is Op.FACTORS:
r = Expr(self.op, dict(self.data))
for k, v in other.data.items():
_pairs_add(r.data, k, v)
return normalize(r)
elif self.op is Op.TERMS:
r = Expr(self.op, {})
for t1, c1 in self.data.items():
for t2, c2 in other.data.items():
_pairs_add(r.data, t1 * t2, c1 * c2)
return normalize(r)
# 处理复数与整数/实数之间的乘法运算
if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL):
return self * as_complex(other)
elif other.op is Op.COMPLEX and self.op in (Op.INTEGER, Op.REAL):
return as_complex(self) * other
elif self.op is Op.REAL and other.op is Op.INTEGER:
return self * as_real(other, kind=self.data[1])
elif self.op is Op.INTEGER and other.op is Op.REAL:
return as_real(self, kind=other.data[1]) * other
# 处理项操作之间的乘法运算
if self.op is Op.TERMS:
return self * as_terms(other)
elif other.op is Op.TERMS:
return as_terms(self) * other
# 如果均不匹配上述条件,则将两个表达式都转换为因子操作进行乘法运算
return as_factors(self) * as_factors(other)
# 如果 other 不是表达式类型,则返回 NotImplemented,表示无法处理此操作
return NotImplemented
# 定义特殊方法 __rmul__,实现右乘运算
def __rmul__(self, other):
# 如果 other 是数值类型,则将其转换为表达式后执行乘法运算
if isinstance(other, number_types):
return as_number(other) * self
# 如果 other 不是数值类型,则返回 NotImplemented,表示无法处理此操作
return NotImplemented
def __pow__(self, other):
other = as_expr(other) # 将参数转换为表达式对象
if isinstance(other, Expr): # 如果参数是表达式对象
if other.op is Op.INTEGER: # 如果表达式操作是整数
exponent = other.data[0] # 获取整数指数
if exponent == 0:
return as_number(1) # 返回 1,任何数的 0 次方为 1
if exponent == 1:
return self # 返回自身,任何数的 1 次方为其本身
if exponent > 0:
if self.op is Op.FACTORS: # 如果自身是因子类型的表达式对象
r = Expr(self.op, {}) # 创建一个新的因子类型表达式对象
for k, v in self.data.items():
r.data[k] = v * exponent # 对每个因子进行指数运算
return normalize(r) # 规范化并返回结果
return self * (self ** (exponent - 1)) # 递归计算正整数次方
elif exponent != -1:
return (self ** (-exponent)) ** -1 # 处理负指数
return Expr(Op.FACTORS, {self: exponent}) # 返回带有自身和指数的新因子表达式
return as_apply(ArithOp.POW, self, other) # 如果不是整数操作,则应用 POW 操作符
return NotImplemented # 如果参数不是表达式对象,则返回 Not Implemented
def __truediv__(self, other):
other = as_expr(other) # 将参数转换为表达式对象
if isinstance(other, Expr): # 如果参数是表达式对象
# Fortran 的 / 操作符与 Python 的 / 不同:
# - 对于整数操作数,/ 是截断操作
return normalize(as_apply(ArithOp.DIV, self, other)) # 应用 DIV 操作符并规范化结果
return NotImplemented # 如果参数不是表达式对象,则返回 Not Implemented
def __rtruediv__(self, other):
other = as_expr(other) # 将参数转换为表达式对象
if isinstance(other, Expr): # 如果参数是表达式对象
return other / self # 执行反向真除操作
return NotImplemented # 如果参数不是表达式对象,则返回 Not Implemented
def __floordiv__(self, other):
other = as_expr(other) # 将参数转换为表达式对象
if isinstance(other, Expr): # 如果参数是表达式对象
# Fortran 的 // 操作符与 Python 的 // 不同:
# - 对于字符串操作数,// 是连接操作
return normalize(Expr(Op.CONCAT, (self, other))) # 创建连接操作的表达式并规范化结果
return NotImplemented # 如果参数不是表达式对象,则返回 Not Implemented
def __rfloordiv__(self, other):
other = as_expr(other) # 将参数转换为表达式对象
if isinstance(other, Expr): # 如果参数是表达式对象
return other // self # 执行反向地板除法操作
return NotImplemented # 如果参数不是表达式对象,则返回 Not Implemented
def __call__(self, *args, **kwargs):
# 在 Fortran 中,括号 () 用于函数调用和索引操作。
#
# TODO: 实现一个方法来决定何时 __call__ 应该返回一个索引表达式。
return as_apply(self, *map(as_expr, args),
**dict((k, as_expr(v)) for k, v in kwargs.items()))
def __getitem__(self, index):
# 用于支持可能包含在 .pyf 文件中的 C 索引操作。
index = as_expr(index) # 将索引转换为表达式对象
if not isinstance(index, tuple):
index = index, # 如果索引不是元组,则转换为单元素元组
if len(index) > 1:
ewarn(f'C-index should be a single expression but got `{index}`') # 发出警告,C 索引应该是单一表达式
return Expr(Op.INDEXING, (self,) + index) # 创建索引操作的表达式并返回
def traverse(self, visit, *args, **kwargs):
"""遍历表达式树并应用访问函数。
访问函数应用于带有给定 args 和 kwargs 的表达式。
如果访问函数返回非空表达式,则遍历调用返回该表达式;
否则,返回一个新的经过标准化处理的表达式,其中包含遍历-访问子表达式。
"""
# 调用访问函数,并根据返回结果判断是否直接返回
result = visit(self, *args, **kwargs)
if result is not None:
return result
# 根据表达式的操作类型进行不同的处理
if self.op in (Op.INTEGER, Op.REAL, Op.STRING, Op.SYMBOL):
return self
elif self.op in (Op.COMPLEX, Op.ARRAY, Op.CONCAT, Op.TERNARY):
# 对于复杂操作类型,递归遍历处理每个子项
return normalize(Expr(self.op, tuple(
item.traverse(visit, *args, **kwargs)
for item in self.data)))
elif self.op in (Op.TERMS, Op.FACTORS):
# 对于项或因子操作类型,遍历处理字典中的每一项
data = {}
for k, v in self.data.items():
k = k.traverse(visit, *args, **kwargs)
v = (v.traverse(visit, *args, **kwargs)
if isinstance(v, Expr) else v)
if k in data:
v = data[k] + v
data[k] = v
return normalize(Expr(self.op, data))
elif self.op is Op.APPLY:
# 对于函数应用操作类型,遍历处理对象、操作数和关键字操作数
obj = self.data[0]
func = (obj.traverse(visit, *args, **kwargs)
if isinstance(obj, Expr) else obj)
operands = tuple(operand.traverse(visit, *args, **kwargs)
for operand in self.data[1])
kwoperands = dict((k, v.traverse(visit, *args, **kwargs))
for k, v in self.data[2].items())
return normalize(Expr(self.op, (func, operands, kwoperands)))
elif self.op is Op.INDEXING:
# 对于索引操作类型,遍历处理对象和索引
obj = self.data[0]
obj = (obj.traverse(visit, *args, **kwargs)
if isinstance(obj, Expr) else obj)
indices = tuple(index.traverse(visit, *args, **kwargs)
for index in self.data[1:])
return normalize(Expr(self.op, (obj,) + indices))
elif self.op in (Op.REF, Op.DEREF):
# 对于引用和解引用操作类型,遍历处理数据
return normalize(Expr(self.op,
self.data.traverse(visit, *args, **kwargs)))
elif self.op is Op.RELATIONAL:
# 对于关系操作类型,遍历处理左操作数和右操作数
rop, left, right = self.data
left = left.traverse(visit, *args, **kwargs)
right = right.traverse(visit, *args, **kwargs)
return normalize(Expr(self.op, (rop, left, right)))
# 若操作类型未被处理,则抛出未实现错误
raise NotImplementedError(f'traverse method for {self.op}')
def contains(self, other):
"""检查 self 是否包含 other。"""
found = []
def visit(expr, found=found):
if found:
return expr
elif expr == other:
found.append(1)
return expr
self.traverse(visit)
# 返回是否找到匹配项的布尔值
return len(found) != 0
def symbols(self):
"""Return a set of symbols contained in self.
"""
found = set() # 初始化一个空集合用于存放找到的符号
def visit(expr, found=found):
if expr.op is Op.SYMBOL: # 如果表达式的操作是符号类型
found.add(expr) # 将符号添加到集合中
self.traverse(visit) # 调用对象的 traverse 方法,传入 visit 函数进行遍历
return found # 返回找到的符号集合
def polynomial_atoms(self):
"""Return a set of expressions used as atoms in polynomial self.
"""
found = set() # 初始化一个空集合用于存放找到的原子表达式
def visit(expr, found=found):
if expr.op is Op.FACTORS: # 如果表达式的操作是因子类型
for b in expr.data: # 遍历因子表达式列表
b.traverse(visit) # 递归调用 traverse 方法继续遍历子表达式
return expr # 返回当前表达式对象
if expr.op in (Op.TERMS, Op.COMPLEX): # 如果操作是项或复合类型,则直接返回
return
if expr.op is Op.APPLY and isinstance(expr.data[0], ArithOp): # 如果是应用操作且第一个数据项是算术操作
if expr.data[0] is ArithOp.POW: # 如果是乘方操作
expr.data[1][0].traverse(visit) # 遍历乘方操作的指数部分
return expr # 返回当前表达式对象
return
if expr.op in (Op.INTEGER, Op.REAL): # 如果是整数或实数类型
return expr # 直接返回表达式对象
found.add(expr) # 将当前表达式对象添加到集合中
if expr.op in (Op.INDEXING, Op.APPLY): # 如果是索引或应用操作
return expr # 返回当前表达式对象
self.traverse(visit) # 调用对象的 traverse 方法,传入 visit 函数进行遍历
return found # 返回找到的原子表达式集合
def linear_solve(self, symbol):
"""Return a, b such that a * symbol + b == self.
If self is not linear with respect to symbol, raise RuntimeError.
"""
b = self.substitute({symbol: as_number(0)}) # 将 symbol 替换为 0,得到常数项 b
ax = self - b # 计算 self 减去常数项 b,得到 a * symbol
a = ax.substitute({symbol: as_number(1)}) # 将 symbol 替换为 1,得到系数 a
zero, _ = as_numer_denom(a * symbol - ax) # 计算 a * symbol - ax 的分子,并获取分母
if zero != as_number(0): # 如果分子不为零
raise RuntimeError(f'not a {symbol}-linear equation:'
f' {a} * {symbol} + {b} == {self}') # 抛出运行时错误,指示不是线性方程
return a, b # 返回计算得到的系数 a 和常数项 b
# 将给定对象标准化并应用基本的求值方法
def normalize(obj):
# 如果对象不是表达式类型,则直接返回该对象
if not isinstance(obj, Expr):
return obj
# 处理表达式类型为 TERMS 的情况
if obj.op is Op.TERMS:
d = {}
# 遍历表达式数据中的每个项和系数
for t, c in obj.data.items():
# 如果系数为0,则跳过该项
if c == 0:
continue
# 如果项的操作类型为 COMPLEX 且系数不为1,则更新项和系数
if t.op is Op.COMPLEX and c != 1:
t = t * c
c = 1
# 如果项的操作类型为 TERMS,则进一步处理其数据项
if t.op is Op.TERMS:
for t1, c1 in t.data.items():
_pairs_add(d, t1, c1 * c)
else:
_pairs_add(d, t, c)
# 如果字典 d 为空,则返回数字0
if len(d) == 0:
# TODO: 确定正确的类型
return as_number(0)
# 如果字典 d 只包含一个项,则返回该项
elif len(d) == 1:
(t, c), = d.items()
if c == 1:
return t
# 否则返回一个表达式对象,操作类型为 TERMS,数据为字典 d
return Expr(Op.TERMS, d)
# 处理表达式类型为 FACTORS 的情况
if obj.op is Op.FACTORS:
coeff = 1
d = {}
# 遍历表达式数据中的每个因子和指数
for b, e in obj.data.items():
# 如果指数为0,则跳过该因子
if e == 0:
continue
# 如果因子的操作类型为 TERMS,且指数为正整数大于1,则展开整数幂
if b.op is Op.TERMS and isinstance(e, integer_types) and e > 1:
b = b * (b ** (e - 1))
e = 1
# 处理因子操作类型为 INTEGER 或 REAL 的情况
if b.op in (Op.INTEGER, Op.REAL):
if e == 1:
coeff *= b.data[0]
elif e > 0:
coeff *= b.data[0] ** e
else:
_pairs_add(d, b, e)
# 处理因子操作类型为 FACTORS 的情况
elif b.op is Op.FACTORS:
if e > 0 and isinstance(e, integer_types):
for b1, e1 in b.data.items():
_pairs_add(d, b1, e1 * e)
else:
_pairs_add(d, b, e)
else:
_pairs_add(d, b, e)
# 如果字典 d 为空或者系数为0,则返回数字0
if len(d) == 0 or coeff == 0:
# TODO: 确定正确的类型
assert isinstance(coeff, number_types)
return as_number(coeff)
# 如果字典 d 只包含一个项,则根据情况返回该项或者表达式对象
elif len(d) == 1:
(b, e), = d.items()
if e == 1:
t = b
else:
t = Expr(Op.FACTORS, d)
if coeff == 1:
return t
return Expr(Op.TERMS, {t: coeff})
# 根据系数是否为1返回表达式对象,操作类型为 FACTORS 或 TERMS
elif coeff == 1:
return Expr(Op.FACTORS, d)
else:
return Expr(Op.TERMS, {Expr(Op.FACTORS, d): coeff})
# 如果对象的操作符是 Op.APPLY,且第一个数据元素是 ArithOp.DIV
if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV:
# 将被除数和除数分配给相应变量
dividend, divisor = obj.data[1]
# 将被除数和除数分别转换为项和系数
t1, c1 = as_term_coeff(dividend)
t2, c2 = as_term_coeff(divisor)
# 如果系数 c1 和 c2 都是整数类型,则计算它们的最大公约数,并进行化简
if isinstance(c1, integer_types) and isinstance(c2, integer_types):
g = gcd(c1, c2)
c1, c2 = c1//g, c2//g
else:
# 如果 c1 或 c2 不是整数类型,则进行浮点数的除法
c1, c2 = c1/c2, 1
# 如果 t1 的操作符是 Op.APPLY,且第一个数据元素是 ArithOp.DIV
if t1.op is Op.APPLY and t1.data[0] is ArithOp.DIV:
# 计算分子和分母
numer = t1.data[1][0] * c1
denom = t1.data[1][1] * t2 * c2
# 返回应用 ArithOp.DIV 操作符的表达式
return as_apply(ArithOp.DIV, numer, denom)
# 如果 t2 的操作符是 Op.APPLY,且第一个数据元素是 ArithOp.DIV
if t2.op is Op.APPLY and t2.data[0] is ArithOp.DIV:
# 计算分子和分母
numer = t2.data[1][1] * t1 * c1
denom = t2.data[1][0] * c2
# 返回应用 ArithOp.DIV 操作符的表达式
return as_apply(ArithOp.DIV, numer, denom)
# 将 t1 和 t2 分解为因子,并构建因子字典
d = dict(as_factors(t1).data)
for b, e in as_factors(t2).data.items():
_pairs_add(d, b, -e)
# 分别构建分子和分母的字典,正负指数对应分子和分母
numer, denom = {}, {}
for b, e in d.items():
if e > 0:
numer[b] = e
else:
denom[b] = -e
# 将分子和分母字典构建为 Op.FACTORS 操作的表达式,然后进行归一化
numer = normalize(Expr(Op.FACTORS, numer)) * c1
denom = normalize(Expr(Op.FACTORS, denom)) * c2
# 如果分母是整数或实数类型,并且值为 1,则返回分子
if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] == 1:
# TODO: denom kind not used
return numer
# 返回应用 ArithOp.DIV 操作符的表达式
return as_apply(ArithOp.DIV, numer, denom)
# 如果对象的操作符是 Op.CONCAT
if obj.op is Op.CONCAT:
# 初始化列表,包含第一个元素
lst = [obj.data[0]]
# 遍历剩余元素
for s in obj.data[1:]:
last = lst[-1]
# 如果最后一个元素和当前元素都是字符串,且可以拼接
if (
last.op is Op.STRING
and s.op is Op.STRING
and last.data[0][0] in '"\''
and s.data[0][0] == last.data[0][-1]
):
# 创建新的字符串表达式,并替换最后一个元素
new_last = as_string(last.data[0][:-1] + s.data[0][1:],
max(last.data[1], s.data[1]))
lst[-1] = new_last
else:
# 否则直接添加当前元素到列表中
lst.append(s)
# 如果列表只有一个元素,则返回该元素
if len(lst) == 1:
return lst[0]
# 返回 Op.CONCAT 操作的表达式,包含所有拼接后的元素
return Expr(Op.CONCAT, tuple(lst))
# 如果对象的操作符是 Op.TERNARY
if obj.op is Op.TERNARY:
# 将条件、表达式1和表达式2进行归一化处理
cond, expr1, expr2 = map(normalize, obj.data)
# 如果条件是整数类型,则根据条件的值返回相应表达式
if cond.op is Op.INTEGER:
return expr1 if cond.data[0] else expr2
# 否则返回 Op.TERNARY 操作的表达式,包含归一化后的条件和表达式
return Expr(Op.TERNARY, (cond, expr1, expr2))
# 默认情况下,直接返回对象本身
return obj
# 将非 Expr 类型的对象转换为 Expr 对象。
def as_expr(obj):
if isinstance(obj, complex):
# 如果 obj 是复数类型,则调用 as_complex 转换为复数表达式
return as_complex(obj.real, obj.imag)
if isinstance(obj, number_types):
# 如果 obj 是数字类型,则调用 as_number 转换为数字表达式
return as_number(obj)
if isinstance(obj, str):
# 如果 obj 是字符串类型,则将其应用 repr 函数转换为带有引号的字符串表达式
return as_string(repr(obj))
if isinstance(obj, tuple):
# 如果 obj 是元组类型,则逐个调用 as_expr 转换为表达式元组
return tuple(map(as_expr, obj))
# 对于其他类型的对象,直接返回该对象
return obj
# 将对象转换为 SYMBOL 表达式(变量或未解析表达式)。
def as_symbol(obj):
return Expr(Op.SYMBOL, obj)
# 将对象转换为 INTEGER 或 REAL 常量。
def as_number(obj, kind=4):
if isinstance(obj, int):
# 如果 obj 是整数类型,则返回 INTEGER 表达式
return Expr(Op.INTEGER, (obj, kind))
if isinstance(obj, float):
# 如果 obj 是浮点数类型,则返回 REAL 表达式
return Expr(Op.REAL, (obj, kind))
if isinstance(obj, Expr):
# 如果 obj 已经是表达式类型,则检查其类型,如果是 INTEGER 或 REAL 直接返回
if obj.op in (Op.INTEGER, Op.REAL):
return obj
# 对于无法转换的情况,抛出 OpError 异常
raise OpError(f'cannot convert {obj} to INTEGER or REAL constant')
# 将对象转换为 INTEGER 常量。
def as_integer(obj, kind=4):
if isinstance(obj, int):
return Expr(Op.INTEGER, (obj, kind))
if isinstance(obj, Expr):
if obj.op is Op.INTEGER:
return obj
raise OpError(f'cannot convert {obj} to INTEGER constant')
# 将对象转换为 REAL 常量。
def as_real(obj, kind=4):
if isinstance(obj, int):
# 如果 obj 是整数类型,则转换为 REAL 表达式
return Expr(Op.REAL, (float(obj), kind))
if isinstance(obj, float):
# 如果 obj 是浮点数类型,则转换为 REAL 表达式
return Expr(Op.REAL, (obj, kind))
if isinstance(obj, Expr):
# 如果 obj 是表达式类型,则根据其类型进行转换
if obj.op is Op.REAL:
return obj
elif obj.op is Op.INTEGER:
# 如果 obj 是 INTEGER 表达式,则将其转换为 REAL 表达式
return Expr(Op.REAL, (float(obj.data[0]), kind))
# 对于无法转换的情况,抛出 OpError 异常
raise OpError(f'cannot convert {obj} to REAL constant')
# 将对象转换为 STRING 表达式(字符串字面常量)。
def as_string(obj, kind=1):
return Expr(Op.STRING, (obj, kind))
# 将对象转换为 ARRAY 表达式(数组常量)。
def as_array(obj):
if isinstance(obj, Expr):
# 如果 obj 已经是表达式类型,则将其转换为元组
obj = obj,
return Expr(Op.ARRAY, obj)
# 将对象转换为 COMPLEX 表达式(复数字面常量)。
def as_complex(real, imag=0):
return Expr(Op.COMPLEX, (as_expr(real), as_expr(imag)))
# 将对象转换为 APPLY 表达式(函数调用、构造函数等)。
def as_apply(func, *args, **kwargs):
return Expr(Op.APPLY,
(func, tuple(map(as_expr, args)),
dict((k, as_expr(v)) for k, v in kwargs.items())))
# 将对象转换为 TERNARY 表达式(三元表达式 cond?expr1:expr2)。
def as_ternary(cond, expr1, expr2):
return Expr(Op.TERNARY, (cond, expr1, expr2))
# 将对象转换为引用表达式。
def as_ref(expr):
return Expr(Op.REF, expr)
# 将对象转换为解引用表达式。
def as_deref(expr):
return Expr(Op.DEREF, expr)
# 返回等于(==)关系表达式。
def as_eq(left, right):
return Expr(Op.RELATIONAL, (RelOp.EQ, left, right))
# 返回一个不等于关系的表达式对象
def as_ne(left, right):
return Expr(Op.RELATIONAL, (RelOp.NE, left, right))
# 返回一个小于关系的表达式对象
def as_lt(left, right):
return Expr(Op.RELATIONAL, (RelOp.LT, left, right))
# 返回一个小于等于关系的表达式对象
def as_le(left, right):
return Expr(Op.RELATIONAL, (RelOp.LE, left, right))
# 返回一个大于关系的表达式对象
def as_gt(left, right):
return Expr(Op.RELATIONAL, (RelOp.GT, left, right))
# 返回一个大于等于关系的表达式对象
def as_ge(left, right):
return Expr(Op.RELATIONAL, (RelOp.GE, left, right))
# 将给定的表达式对象转换为TERMS表达式对象
def as_terms(obj):
"""Return expression as TERMS expression.
"""
if isinstance(obj, Expr):
obj = normalize(obj)
# 如果表达式已经是TERMS类型,则直接返回
if obj.op is Op.TERMS:
return obj
# 如果表达式是INTEGER类型,则转换为TERMS类型
if obj.op is Op.INTEGER:
return Expr(Op.TERMS, {as_integer(1, obj.data[1]): obj.data[0]})
# 如果表达式是REAL类型,则转换为TERMS类型
if obj.op is Op.REAL:
return Expr(Op.TERMS, {as_real(1, obj.data[1]): obj.data[0]})
# 否则,默认将表达式转换为一个包含单个项的TERMS表达式
return Expr(Op.TERMS, {obj: 1})
# 如果不是Expr类型,则抛出异常
raise OpError(f'cannot convert {type(obj)} to terms Expr')
# 将给定的表达式对象转换为FACTORS表达式对象
def as_factors(obj):
"""Return expression as FACTORS expression.
"""
if isinstance(obj, Expr):
obj = normalize(obj)
# 如果表达式已经是FACTORS类型,则直接返回
if obj.op is Op.FACTORS:
return obj
# 如果表达式是TERMS类型,且只包含一个项,则转换为FACTORS类型
if obj.op is Op.TERMS:
if len(obj.data) == 1:
(term, coeff), = obj.data.items()
if coeff == 1:
return Expr(Op.FACTORS, {term: 1})
return Expr(Op.FACTORS, {term: 1, Expr.number(coeff): 1})
# 如果表达式是APPLY类型,并且是除法操作,则转换为FACTORS类型
if (obj.op is Op.APPLY
and obj.data[0] is ArithOp.DIV
and not obj.data[2]):
return Expr(Op.FACTORS, {obj.data[1][0]: 1, obj.data[1][1]: -1})
# 否则,默认将表达式转换为一个包含单个因子的FACTORS表达式
return Expr(Op.FACTORS, {obj: 1})
# 如果不是Expr类型,则抛出异常
raise OpError(f'cannot convert {type(obj)} to terms Expr')
# 将给定的表达式对象转换为项-系数对形式
def as_term_coeff(obj):
"""Return expression as term-coefficient pair.
"""
if isinstance(obj, Expr):
obj = normalize(obj)
# 如果表达式是INTEGER类型,则转换为项-系数对形式
if obj.op is Op.INTEGER:
return as_integer(1, obj.data[1]), obj.data[0]
# 如果表达式是REAL类型,则转换为项-系数对形式
if obj.op is Op.REAL:
return as_real(1, obj.data[1]), obj.data[0]
# 如果表达式是TERMS类型,且只包含一个项,则返回项和系数
if obj.op is Op.TERMS:
if len(obj.data) == 1:
(term, coeff), = obj.data.items()
return term, coeff
# TODO: 找到系数的最大公约数
# 如果表达式是APPLY类型,并且是除法操作,则转换为项-系数对形式
if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV:
t, c = as_term_coeff(obj.data[1][0])
return as_apply(ArithOp.DIV, t, obj.data[1][1]), c
# 否则,默认将表达式直接返回,并假设系数为1
return obj, 1
# 如果不是Expr类型,则抛出异常
raise OpError(f'cannot convert {type(obj)} to term and coeff')
# 将给定的表达式对象转换为数值-分母对形式
def as_numer_denom(obj):
"""Return expression as numer-denom pair.
"""
# 检查对象是否属于表达式类
if isinstance(obj, Expr):
# 对象规范化,确保其符合标准形式
obj = normalize(obj)
# 检查操作符是否属于以下类型之一:整数、实数、复数、符号、索引、三元操作符
if obj.op in (Op.INTEGER, Op.REAL, Op.COMPLEX, Op.SYMBOL,
Op.INDEXING, Op.TERNARY):
# 返回对象和数值 1
return obj, as_number(1)
# 如果操作符是应用操作
elif obj.op is Op.APPLY:
# 如果对象表示的是除法且第三个数据为空
if obj.data[0] is ArithOp.DIV and not obj.data[2]:
# 将操作数转换为分子和分母,然后返回其交叉乘积
numers, denoms = map(as_numer_denom, obj.data[1])
return numers[0] * denoms[1], numers[1] * denoms[0]
# 返回对象和数值 1
return obj, as_number(1)
# 如果操作符是项操作
elif obj.op is Op.TERMS:
# 初始化数值列表
numers, denoms = [], []
# 遍历项数据中的每一项及其系数
for term, coeff in obj.data.items():
# 将每个项转换为分子和分母形式
n, d = as_numer_denom(term)
# 根据系数调整分子值,并添加到分子列表中
n = n * coeff
numers.append(n)
# 添加分母到分母列表中
denoms.append(d)
# 初始化数值并计算总分子和总分母
numer, denom = as_number(0), as_number(1)
for i in range(len(numers)):
n = numers[i]
for j in range(len(numers)):
if i != j:
# 对非当前项的分母应用乘法
n *= denoms[j]
# 添加到总分子中
numer += n
# 对所有分母应用乘法
denom *= denoms[i]
# 如果总分母为整数或实数且为负数,则调整总分子和总分母的符号
if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] < 0:
numer, denom = -numer, -denom
# 返回最终计算结果的分子和分母
return numer, denom
# 如果操作符是因子操作
elif obj.op is Op.FACTORS:
# 初始化分子和分母为 1
numer, denom = as_number(1), as_number(1)
# 遍历因子数据中的每一对底数和指数
for b, e in obj.data.items():
# 将每个底数转换为分子和分母形式
bnumer, bdenom = as_numer_denom(b)
# 根据指数值调整分子和分母
if e > 0:
numer *= bnumer ** e
denom *= bdenom ** e
elif e < 0:
numer *= bdenom ** (-e)
denom *= bnumer ** (-e)
# 返回最终计算结果的分子和分母
return numer, denom
# 如果对象类型无法转换为分子和分母,则引发操作错误异常
raise OpError(f'cannot convert {type(obj)} to numer and denom')
def _counter():
# Used internally to generate unique dummy symbols
counter = 0
while True:
counter += 1
yield counter
# Initialize a global counter generator
COUNTER = _counter()
def eliminate_quotes(s):
"""Replace quoted substrings of input string.
Return a new string and a mapping of replacements.
"""
d = {}
def repl(m):
kind, value = m.groups()[:2]
if kind:
# remove trailing underscore
kind = kind[:-1]
# Determine if the quote is single or double and create a unique key
p = {"'": "SINGLE", '"': "DOUBLE"}[value[0]]
k = f'{kind}@__f2py_QUOTES_{p}_{COUNTER.__next__()}@'
d[k] = value
return k
# Replace quoted substrings in the input string 's' using the repl function
new_s = re.sub(r'({kind}_|)({single_quoted}|{double_quoted})'.format(
kind=r'\w[\w\d_]*',
single_quoted=r"('([^'\\]|(\\.))*')",
double_quoted=r'("([^"\\]|(\\.))*")'),
repl, s)
# Ensure no quotes remain in the new string
assert '"' not in new_s
assert "'" not in new_s
return new_s, d
def insert_quotes(s, d):
"""Inverse of eliminate_quotes.
"""
# Replace the unique keys back with their original quoted values in string 's'
for k, v in d.items():
kind = k[:k.find('@')]
if kind:
kind += '_'
s = s.replace(k, kind + v)
return s
def replace_parenthesis(s):
"""Replace substrings of input that are enclosed in parenthesis.
Return a new string and a mapping of replacements.
"""
left, right = None, None
mn_i = len(s)
# Iterate through possible parenthesis pairs to find the first occurrence
for left_, right_ in (('(/', '/)'), '()', '{}', '[]'):
i = s.find(left_)
if i == -1:
continue
if i < mn_i:
mn_i = i
left, right = left_, right_
# If no parenthesis pairs are found, return original string and empty dictionary
if left is None:
return s, {}
i = mn_i
j = s.find(right, i)
# Ensure balanced parenthesis within the found pair
while s.count(left, i + 1, j) != s.count(right, i + 1, j):
j = s.find(right, j + 1)
if j == -1:
raise ValueError(f'Mismatch of {left+right} parenthesis in {s!r}')
# Determine the type of parenthesis and create a unique key
p = {'(': 'ROUND', '[': 'SQUARE', '{': 'CURLY', '(/': 'ROUNDDIV'}[left]
k = f'@__f2py_PARENTHESIS_{p}_{COUNTER.__next__()}@'
v = s[i+len(left):j]
# Recursively replace parenthesis in the remainder of the string
r, d = replace_parenthesis(s[j+len(right):])
d[k] = v
return s[:i] + k + r, d
def _get_parenthesis_kind(s):
assert s.startswith('@__f2py_PARENTHESIS_'), s
return s.split('_')[4]
def unreplace_parenthesis(s, d):
"""Inverse of replace_parenthesis.
"""
# Replace unique keys with their respective parenthesis and enclosed values
for k, v in d.items():
p = _get_parenthesis_kind(k)
left = dict(ROUND='(', SQUARE='[', CURLY='{', ROUNDDIV='(/')[p]
right = dict(ROUND=')', SQUARE=']', CURLY='}', ROUNDDIV='/)')[p]
s = s.replace(k, left + v + right)
return s
def fromstring(s, language=Language.C):
"""Create an expression from a string.
This is a "lazy" parser, that is, only arithmetic operations are
resolved, non-arithmetic operations are treated as symbols.
"""
# 使用指定的语言(language)创建一个 _FromStringWorker 实例,并解析字符串 s
r = _FromStringWorker(language=language).parse(s)
# 如果解析结果 r 是 Expr 类型的对象,则直接返回它
if isinstance(r, Expr):
return r
# 如果解析结果 r 不是 Expr 类型的对象,则抛出 ValueError 异常,指示解析失败
raise ValueError(f'failed to parse `{s}` to Expr instance: got `{r}`')
class _Pair:
# Internal class to represent a pair of expressions
def __init__(self, left, right):
# Constructor to initialize a _Pair object with left and right expressions
self.left = left
self.right = right
def substitute(self, symbols_map):
# Method to substitute expressions with symbols from symbols_map
left, right = self.left, self.right
if isinstance(left, Expr):
left = left.substitute(symbols_map)
if isinstance(right, Expr):
right = right.substitute(symbols_map)
return _Pair(left, right)
def __repr__(self):
# Returns a string representation of the _Pair object
return f'{type(self).__name__}({self.left}, {self.right})'
class _FromStringWorker:
def __init__(self, language=Language.C):
# Constructor to initialize _FromStringWorker object with optional language parameter
self.original = None
self.quotes_map = None
self.language = language
def finalize_string(self, s):
# Method to finalize string by inserting quotes according to quotes_map
return insert_quotes(s, self.quotes_map)
def parse(self, inp):
# Method to parse input string inp
self.original = inp
# Eliminate quotes from inp, store unquoted version in unquoted, quotes mapping in quotes_map
unquoted, self.quotes_map = eliminate_quotes(inp)
# Process the unquoted string and return the result
return self.process(unquoted)
.\numpy\numpy\f2py\tests\src\array_from_pyobj\wrapmodule.c
/*
* This file was auto-generated with f2py (version:2_1330) and hand edited by
* Pearu for testing purposes. Do not edit this file unless you know what you
* are doing!!!
*/
extern "C" {
/*********************** See f2py2e/cfuncs.py: includes ***********************/
// 定义包裹错误和包裹模块对象
static PyObject *wrap_error;
static PyObject *wrap_module;
/************************************ call ************************************/
// 函数签名文档字符串
static char doc_f2py_rout_wrap_call[] = "\
Function signature:\n\
arr = call(type_num,dims,intent,obj)\n\
Required arguments:\n"
" type_num : input int\n"
" dims : input int-sequence\n"
" intent : input int\n"
" obj : input python object\n"
"Return objects:\n"
" arr : array";
// 包裹函数,调用Fortran函数并返回结果
static PyObject *f2py_rout_wrap_call(PyObject *capi_self,
PyObject *capi_args) {
PyObject * volatile capi_buildvalue = NULL;
int type_num = 0;
int elsize = 0;
npy_intp *dims = NULL;
PyObject *dims_capi = Py_None;
int rank = 0;
int intent = 0;
PyArrayObject *capi_arr_tmp = NULL;
PyObject *arr_capi = Py_None;
int i;
// 解析参数元组
if (!PyArg_ParseTuple(capi_args,"iiOiO|:wrap.call",\
&type_num,&elsize,&dims_capi,&intent,&arr_capi))
return NULL;
// 获取序列的长度作为数组的维度
rank = PySequence_Length(dims_capi);
dims = malloc(rank*sizeof(npy_intp));
// 遍历序列,获取每个维度的值
for (i=0;i<rank;++i) {
PyObject *tmp;
tmp = PySequence_GetItem(dims_capi, i);
if (tmp == NULL) {
goto fail;
}
dims[i] = (npy_intp)PyLong_AsLong(tmp);
Py_DECREF(tmp);
if (dims[i] == -1 && PyErr_Occurred()) {
goto fail;
}
}
// 调用ndarray_from_pyobj函数,将Python对象转换为NumPy数组对象
capi_arr_tmp = ndarray_from_pyobj(type_num,elsize,dims,rank,intent|F2PY_INTENT_OUT,arr_capi,"wrap.call failed");
if (capi_arr_tmp == NULL) {
free(dims);
return NULL;
}
// 构建返回值,包含NumPy数组对象
capi_buildvalue = Py_BuildValue("N",capi_arr_tmp);
free(dims);
return capi_buildvalue;
fail:
free(dims);
return NULL;
}
/************************************ attrs ************************************/
// 函数签名文档字符串
static char doc_f2py_rout_wrap_attrs[] = "\
Function signature:\n\
arr = array_attrs(arr)\n\
Required arguments:\n"
" arr : input array object\n"
"Return objects:\n"
" data : data address in hex\n"
" nd : int\n"
" dimensions : tuple\n"
" strides : tuple\n"
" base : python object\n"
" (kind,type,type_num,elsize,alignment) : 4-tuple\n"
" flags : int\n"
" itemsize : int\n"
;
// 包裹函数,返回NumPy数组对象的属性信息
static PyObject *f2py_rout_wrap_attrs(PyObject *capi_self,
PyObject *capi_args) {
PyObject *arr_capi = Py_None;
PyArrayObject *arr = NULL;
PyObject *dimensions = NULL;
PyObject *strides = NULL;
char s[100];
int i;
memset(s,0,100);
// 解析参数元组,验证输入参数是否正确
if (!PyArg_ParseTuple(capi_args,"O!|:wrap.attrs",
&PyArray_Type,&arr_capi))
return NULL;
// 将Python对象转换为NumPy数组对象
arr = (PyArrayObject *)arr_capi;
// 将数据地址转换为十六进制字符串
sprintf(s,"%p",PyArray_DATA(arr));
// 创建维度元组和步长元组
dimensions = PyTuple_New(PyArray_NDIM(arr));
strides = PyTuple_New(PyArray_NDIM(arr));
// 遍历数组的维度,获取每个维度和步长
for (i=0;i<PyArray_NDIM(arr);++i) {
PyTuple_SetItem(dimensions, i, PyLong_FromLong(PyArray_DIM(arr, i)));
PyTuple_SetItem(strides, i, PyLong_FromLong(PyArray_STRIDE(arr, i)));
}
return Py_BuildValue("siNNO(cciii)ii", s, PyArray_NDIM(arr),
dimensions, strides,
(PyArray_BASE(arr)==NULL ? Py_None : PyArray_BASE(arr)),
PyArray_DESCR(arr)->kind,
PyArray_DESCR(arr)->type,
PyArray_TYPE(arr),
PyArray_ITEMSIZE(arr),
PyArray_DESCR(arr)->alignment,
PyArray_FLAGS(arr),
PyArray_ITEMSIZE(arr));
}
static PyMethodDef f2py_module_methods[] = {
// 定义 Python 模块中的方法列表,每个条目包含方法名、函数指针、参数类型和文档字符串
{"call", f2py_rout_wrap_call, METH_VARARGS, doc_f2py_rout_wrap_call},
{"array_attrs", f2py_rout_wrap_attrs, METH_VARARGS, doc_f2py_rout_wrap_attrs},
{NULL, NULL} // 方法列表结束标记
};
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, // 初始化 Python 模块定义结构
"test_array_from_pyobj_ext", // 模块名称
NULL, // 模块文档字符串(此处为NULL)
-1, // 模块状态(此处为-1)
f2py_module_methods, // 指向模块方法列表的指针
NULL, // 模块的全局状态
NULL, // 模块的方法定义结构
NULL, // 模块的每个方法的文档字符串
NULL // 模块的清理函数
};
PyMODINIT_FUNC PyInit_test_array_from_pyobj_ext(void) {
PyObject *m, *d, *s;
// 创建 Python 模块对象,并将其赋值给变量 m 和 wrap_module
m = wrap_module = PyModule_Create(&moduledef);
// 将 PyFortran_Type 类型设置为 PyType_Type 类型
Py_SET_TYPE(&PyFortran_Type, &PyType_Type);
// 导入 NumPy 库
import_array();
// 检查是否有异常发生,若有则致命错误
if (PyErr_Occurred())
Py_FatalError("can't initialize module wrap (failed to import numpy)");
// 获取模块的字典对象
d = PyModule_GetDict(m);
// 创建包含模块说明的 Unicode 字符串对象,并设置为模块的 __doc__ 属性
s = PyUnicode_FromString("This module 'wrap' is auto-generated with f2py (version:2_1330).\nFunctions:\n"
" arr = call(type_num,dims,intent,obj)\n"
".");
PyDict_SetItemString(d, "__doc__", s);
// 创建 wrap.error 异常并设置到模块的全局字典中
wrap_error = PyErr_NewException("wrap.error", NULL, NULL);
Py_DECREF(s);
// 定义宏 ADDCONST,用于向模块的字典中添加常量
s = PyLong_FromLong(CONST); \
PyDict_SetItemString(d, NAME, s); \
// 以下代码可能继续定义各种常量,并添加到模块字典中
// 释放 Python 对象 s 的引用计数
Py_DECREF(s)
// 添加常量 "F2PY_INTENT_IN" 到模块中
ADDCONST("F2PY_INTENT_IN", F2PY_INTENT_IN);
// 添加常量 "F2PY_INTENT_INOUT" 到模块中
ADDCONST("F2PY_INTENT_INOUT", F2PY_INTENT_INOUT);
// 添加常量 "F2PY_INTENT_OUT" 到模块中
ADDCONST("F2PY_INTENT_OUT", F2PY_INTENT_OUT);
// 添加常量 "F2PY_INTENT_HIDE" 到模块中
ADDCONST("F2PY_INTENT_HIDE", F2PY_INTENT_HIDE);
// 添加常量 "F2PY_INTENT_CACHE" 到模块中
ADDCONST("F2PY_INTENT_CACHE", F2PY_INTENT_CACHE);
// 添加常量 "F2PY_INTENT_COPY" 到模块中
ADDCONST("F2PY_INTENT_COPY", F2PY_INTENT_COPY);
// 添加常量 "F2PY_INTENT_C" 到模块中
ADDCONST("F2PY_INTENT_C", F2PY_INTENT_C);
// 添加常量 "F2PY_OPTIONAL" 到模块中
ADDCONST("F2PY_OPTIONAL", F2PY_OPTIONAL);
// 添加常量 "F2PY_INTENT_INPLACE" 到模块中
ADDCONST("F2PY_INTENT_INPLACE", F2PY_INTENT_INPLACE);
// 添加常量 "NPY_BOOL" 到模块中
ADDCONST("NPY_BOOL", NPY_BOOL);
// 添加常量 "NPY_BYTE" 到模块中
ADDCONST("NPY_BYTE", NPY_BYTE);
// 添加常量 "NPY_UBYTE" 到模块中
ADDCONST("NPY_UBYTE", NPY_UBYTE);
// 添加常量 "NPY_SHORT" 到模块中
ADDCONST("NPY_SHORT", NPY_SHORT);
// 添加常量 "NPY_USHORT" 到模块中
ADDCONST("NPY_USHORT", NPY_USHORT);
// 添加常量 "NPY_INT" 到模块中
ADDCONST("NPY_INT", NPY_INT);
// 添加常量 "NPY_UINT" 到模块中
ADDCONST("NPY_UINT", NPY_UINT);
// 添加常量 "NPY_INTP" 到模块中
ADDCONST("NPY_INTP", NPY_INTP);
// 添加常量 "NPY_UINTP" 到模块中
ADDCONST("NPY_UINTP", NPY_UINTP);
// 添加常量 "NPY_LONG" 到模块中
ADDCONST("NPY_LONG", NPY_LONG);
// 添加常量 "NPY_ULONG" 到模块中
ADDCONST("NPY_ULONG", NPY_ULONG);
// 添加常量 "NPY_LONGLONG" 到模块中
ADDCONST("NPY_LONGLONG", NPY_LONGLONG);
// 添加常量 "NPY_ULONGLONG" 到模块中
ADDCONST("NPY_ULONGLONG", NPY_ULONGLONG);
// 添加常量 "NPY_FLOAT" 到模块中
ADDCONST("NPY_FLOAT", NPY_FLOAT);
// 添加常量 "NPY_DOUBLE" 到模块中
ADDCONST("NPY_DOUBLE", NPY_DOUBLE);
// 添加常量 "NPY_LONGDOUBLE" 到模块中
ADDCONST("NPY_LONGDOUBLE", NPY_LONGDOUBLE);
// 添加常量 "NPY_CFLOAT" 到模块中
ADDCONST("NPY_CFLOAT", NPY_CFLOAT);
// 添加常量 "NPY_CDOUBLE" 到模块中
ADDCONST("NPY_CDOUBLE", NPY_CDOUBLE);
// 添加常量 "NPY_CLONGDOUBLE" 到模块中
ADDCONST("NPY_CLONGDOUBLE", NPY_CLONGDOUBLE);
// 添加常量 "NPY_OBJECT" 到模块中
ADDCONST("NPY_OBJECT", NPY_OBJECT);
// 添加常量 "NPY_STRING" 到模块中
ADDCONST("NPY_STRING", NPY_STRING);
// 添加常量 "NPY_UNICODE" 到模块中
ADDCONST("NPY_UNICODE", NPY_UNICODE);
// 添加常量 "NPY_VOID" 到模块中
ADDCONST("NPY_VOID", NPY_VOID);
// 添加常量 "NPY_NTYPES_LEGACY" 到模块中
ADDCONST("NPY_NTYPES_LEGACY", NPY_NTYPES_LEGACY);
// 添加常量 "NPY_NOTYPE" 到模块中
ADDCONST("NPY_NOTYPE", NPY_NOTYPE);
// 添加常量 "NPY_USERDEF" 到模块中
ADDCONST("NPY_USERDEF", NPY_USERDEF);
// 添加常量 "CONTIGUOUS" 到模块中
ADDCONST("CONTIGUOUS", NPY_ARRAY_C_CONTIGUOUS);
// 添加常量 "FORTRAN" 到模块中
ADDCONST("FORTRAN", NPY_ARRAY_F_CONTIGUOUS);
// 添加常量 "OWNDATA" 到模块中
ADDCONST("OWNDATA", NPY_ARRAY_OWNDATA);
// 添加常量 "FORCECAST" 到模块中
ADDCONST("FORCECAST", NPY_ARRAY_FORCECAST);
// 添加常量 "ENSURECOPY" 到模块中
ADDCONST("ENSURECOPY", NPY_ARRAY_ENSURECOPY);
// 添加常量 "ENSUREARRAY" 到模块中
ADDCONST("ENSUREARRAY", NPY_ARRAY_ENSUREARRAY);
// 添加常量 "ALIGNED" 到模块中
ADDCONST("ALIGNED", NPY_ARRAY_ALIGNED);
// 添加常量 "WRITEABLE" 到模块中
ADDCONST("WRITEABLE", NPY_ARRAY_WRITEABLE);
// 添加常量 "WRITEBACKIFCOPY" 到模块中
ADDCONST("WRITEBACKIFCOPY", NPY_ARRAY_WRITEBACKIFCOPY);
// 添加常量 "BEHAVED" 到模块中
ADDCONST("BEHAVED", NPY_ARRAY_BEHAVED);
// 添加常量 "BEHAVED_NS" 到模块中
ADDCONST("BEHAVED_NS", NPY_ARRAY_BEHAVED_NS);
// 添加常量 "CARRAY" 到模块中
ADDCONST("CARRAY", NPY_ARRAY_CARRAY);
// 添加常量 "FARRAY" 到模块中
ADDCONST("FARRAY", NPY_ARRAY_FARRAY);
// 添加常量 "CARRAY_RO" 到模块中
ADDCONST("CARRAY_RO", NPY_ARRAY_CARRAY_RO);
// 添加常量 "FARRAY_RO" 到模块中
ADDCONST("FARRAY_RO", NPY_ARRAY_FARRAY_RO);
// 添加常量 "DEFAULT" 到模块中
ADDCONST("DEFAULT", NPY_ARRAY_DEFAULT);
// 添加常量 "UPDATE_ALL" 到模块中
ADDCONST("UPDATE_ALL", NPY_ARRAY_UPDATE_ALL);
取消定义一个名为ADDCONST的宏。在这里,可能是取消定义某个在此之前定义的宏。
if (PyErr_Occurred())
Py_FatalError("can't initialize module wrap");
如果Python中发生了错误,调用Py_FatalError函数,程序将无法初始化模块wrap。
on_exit(f2py_report_on_exit,(void*)"array_from_pyobj.wrap.call");
如果已定义了宏F2PY_REPORT_ATEXIT,则在程序退出时调用on_exit函数,传递f2py_report_on_exit函数和字符串"array_from_pyobj.wrap.call"作为参数。
return m;
}
}
返回模块对象m并结束当前函数。如果是C++环境,则关闭extern "C"的语言链接规范。
.\numpy\numpy\f2py\tests\test_abstract_interface.py
from pathlib import Path
import pytest
import textwrap
from . import util
from numpy.f2py import crackfortran
from numpy.testing import IS_WASM
@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
@pytest.mark.slow
class TestAbstractInterface(util.F2PyTest):
sources = [util.getpath("tests", "src", "abstract_interface", "foo.f90")]
skip = ["add1", "add2"]
def test_abstract_interface(self):
assert self.module.ops_module.foo(3, 5) == (8, 13)
def test_parse_abstract_interface(self):
fpath = util.getpath("tests", "src", "abstract_interface", "gh18403_mod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
assert len(mod[0]["body"]) == 1
assert mod[0]["body"][0]["block"] == "abstract interface"
.\numpy\numpy\f2py\tests\test_array_from_pyobj.py
import os
import sys
import copy
import platform
import pytest
from pathlib import Path
import numpy as np
from numpy.testing import assert_, assert_equal
from numpy._core._type_aliases import c_names_dict as _c_names_dict
from . import util
wrap = None
c_names_dict = dict(
CHARACTER=np.dtype("c"),
**_c_names_dict
)
def get_testdir():
testroot = Path(__file__).resolve().parent / "src"
return testroot / "array_from_pyobj"
def setup_module():
"""
构建必需的测试扩展模块
"""
global wrap
if wrap is None:
src = [
get_testdir() / "wrapmodule.c",
]
wrap = util.build_meson(src, module_name="test_array_from_pyobj_ext")
def flags_info(arr):
flags = wrap.array_attrs(arr)[6]
return flags2names(flags)
def flags2names(flags):
info = []
for flagname in [
"CONTIGUOUS",
"FORTRAN",
"OWNDATA",
"ENSURECOPY",
"ENSUREARRAY",
"ALIGNED",
"NOTSWAPPED",
"WRITEABLE",
"WRITEBACKIFCOPY",
"UPDATEIFCOPY",
"BEHAVED",
"BEHAVED_RO",
"CARRAY",
"FARRAY",
]:
if abs(flags) & getattr(wrap, flagname, 0):
info.append(flagname)
return info
class Intent:
def __init__(self, intent_list=[]):
self.intent_list = intent_list[:]
flags = 0
for i in intent_list:
if i == "optional":
flags |= wrap.F2PY_OPTIONAL
else:
flags |= getattr(wrap, "F2PY_INTENT_" + i.upper())
self.flags = flags
def __getattr__(self, name):
name = name.lower()
if name == "in_":
name = "in"
return self.__class__(self.intent_list + [name])
def __str__(self):
return "intent(%s)" % (",".join(self.intent_list))
def __repr__(self):
return "Intent(%r)" % (self.intent_list)
def is_intent(self, *names):
for name in names:
if name not in self.intent_list:
return False
return True
def is_intent_exact(self, *names):
return len(self.intent_list) == len(names) and self.is_intent(*names)
_type_names = [
"BOOL",
"BYTE",
"UBYTE",
"SHORT",
"USHORT",
"INT",
"UINT",
"LONG",
"ULONG",
"LONGLONG",
"ULONGLONG",
"FLOAT",
"DOUBLE",
"CFLOAT",
"STRING1",
"STRING5",
"CHARACTER",
]
_cast_dict = {
"BOOL": ["BOOL"],
"BYTE": _cast_dict["BOOL"] + ["BYTE"],
"UBYTE": _cast_dict["BOOL"] + ["UBYTE"],
"BYTE": ["BYTE"],
"UBYTE": ["UBYTE"],
"SHORT": _cast_dict["BYTE"] + ["UBYTE", "SHORT"],
"USHORT": _cast_dict["UBYTE"] + ["BYTE", "USHORT"],
"INT": _cast_dict["SHORT"] + ["USHORT", "INT"],
}
_cast_dict["UINT"] = _cast_dict["USHORT"] + ["SHORT", "UINT"]
_cast_dict["LONG"] = _cast_dict["INT"] + ["LONG"]
_cast_dict["ULONG"] = _cast_dict["UINT"] + ["ULONG"]
_cast_dict["LONGLONG"] = _cast_dict["LONG"] + ["LONGLONG"]
_cast_dict["ULONGLONG"] = _cast_dict["ULONG"] + ["ULONGLONG"]
_cast_dict["FLOAT"] = _cast_dict["SHORT"] + ["USHORT", "FLOAT"]
_cast_dict["DOUBLE"] = _cast_dict["INT"] + ["UINT", "FLOAT", "DOUBLE"]
_cast_dict["CFLOAT"] = _cast_dict["FLOAT"] + ["CFLOAT"]
_cast_dict['STRING1'] = ['STRING1']
_cast_dict['STRING5'] = ['STRING5']
_cast_dict['CHARACTER'] = ['CHARACTER']
if ((np.intp().dtype.itemsize != 4 or np.clongdouble().dtype.alignment <= 8)
and sys.platform != "win32"
and (platform.system(), platform.processor()) != ("Darwin", "arm")):
_type_names.extend(["LONGDOUBLE", "CDOUBLE", "CLONGDOUBLE"])
_cast_dict["LONGDOUBLE"] = _cast_dict["LONG"] + [
"ULONG",
"FLOAT",
"DOUBLE",
"LONGDOUBLE",
]
_cast_dict["CLONGDOUBLE"] = _cast_dict["LONGDOUBLE"] + [
"CFLOAT",
"CDOUBLE",
"CLONGDOUBLE",
]
_cast_dict["CDOUBLE"] = _cast_dict["DOUBLE"] + ["CFLOAT", "CDOUBLE"]
class Type:
_type_cache = {}
def __new__(cls, name):
if isinstance(name, np.dtype):
dtype0 = name
name = None
for n, i in c_names_dict.items():
if not isinstance(i, type) and dtype0.type is i.type:
name = n
break
obj = cls._type_cache.get(name.upper(), None)
if obj is not None:
return obj
obj = object.__new__(cls)
obj._init(name)
cls._type_cache[name.upper()] = obj
return obj
def _init(self, name):
self.NAME = name.upper()
if self.NAME == 'CHARACTER':
info = c_names_dict[self.NAME]
self.type_num = getattr(wrap, 'NPY_STRING')
self.elsize = 1
self.dtype = np.dtype('c')
elif self.NAME.startswith('STRING'):
info = c_names_dict[self.NAME[:6]]
self.type_num = getattr(wrap, 'NPY_STRING')
self.elsize = int(self.NAME[6:] or 0)
self.dtype = np.dtype(f'S{self.elsize}')
else:
info = c_names_dict[self.NAME]
self.type_num = getattr(wrap, 'NPY_' + self.NAME)
self.elsize = info.itemsize
self.dtype = np.dtype(info.type)
assert self.type_num == info.num
self.type = info.type
self.dtypechar = info.char
def __repr__(self):
return (f"Type({self.NAME})|type_num={self.type_num},"
f" dtype={self.dtype},"
f" type={self.type}, elsize={self.elsize},"
f" dtypechar={self.dtypechar}")
def cast_types(self):
return [self.__class__(_m) for _m in _cast_dict[self.NAME]]
def all_types(self):
return [self.__class__(_m) for _m in _type_names]
def smaller_types(self):
bits = c_names_dict[self.NAME].alignment
types = []
for name in _type_names:
if c_names_dict[name].alignment < bits:
types.append(Type(name))
return types
def equal_types(self):
bits = c_names_dict[self.NAME].alignment
types = []
for name in _type_names:
if name == self.NAME:
continue
if c_names_dict[name].alignment == bits:
types.append(Type(name))
return types
def larger_types(self):
bits = c_names_dict[self.NAME].alignment
types = []
for name in _type_names:
if c_names_dict[name].alignment > bits:
types.append(Type(name))
return types
class Array:
def __repr__(self):
return (f'Array({self.type}, {self.dims}, {self.intent},'
f' {self.obj})|arr={self.arr}')
def arr_equal(self, arr1, arr2):
if arr1.shape != arr2.shape:
return False
return (arr1 == arr2).all()
def __str__(self):
return str(self.arr)
def has_shared_memory(self):
if self.obj is self.arr:
return True
if not isinstance(self.obj, np.ndarray):
return False
obj_attr = wrap.array_attrs(self.obj)
return obj_attr[0] == self.arr_attr[0]
class TestIntent:
def test_in_out(self):
assert str(intent.in_.out) == "intent(in,out)"
assert intent.in_.c.is_intent("c")
assert not intent.in_.c.is_intent_exact("c")
assert intent.in_.c.is_intent_exact("c", "in")
assert intent.in_.c.is_intent_exact("in", "c")
assert not intent.in_.is_intent("c")
class TestSharedMemory:
@pytest.fixture(autouse=True, scope="class", params=_type_names)
def setup_type(self, request):
request.cls.type = Type(request.param)
request.cls.array = lambda self, dims, intent, obj: Array(
Type(request.param), dims, intent, obj)
@property
def num2seq(self):
if self.type.NAME.startswith('STRING'):
elsize = self.type.elsize
return ['1' * elsize, '2' * elsize]
return [1, 2]
@property
def num23seq(self):
if self.type.NAME.startswith('STRING'):
elsize = self.type.elsize
return [['1' * elsize, '2' * elsize, '3' * elsize],
['4' * elsize, '5' * elsize, '6' * elsize]]
return [[1, 2, 3], [4, 5, 6]]
def test_in_from_2seq(self):
a = self.array([2], intent.in_, self.num2seq)
assert not a.has_shared_memory()
def test_in_from_2casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num2seq, dtype=t.dtype)
a = self.array([len(self.num2seq)], intent.in_, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory(), repr((self.type.dtype, t.dtype))
else:
assert not a.has_shared_memory()
@pytest.mark.parametrize("write", ["w", "ro"])
@pytest.mark.parametrize("order", ["C", "F"])
@pytest.mark.parametrize("inp", ["2seq", "23seq"])
def test_in_nocopy(self, write, order, inp):
"""Test if intent(in) array can be passed without copies"""
seq = getattr(self, "num" + inp)
obj = np.array(seq, dtype=self.type.dtype, order=order)
obj.setflags(write=(write == 'w'))
a = self.array(obj.shape,
((order == 'C' and intent.in_.c) or intent.in_), obj)
assert a.has_shared_memory()
def test_inout_2seq(self):
obj = np.array(self.num2seq, dtype=self.type.dtype)
a = self.array([len(self.num2seq)], intent.inout, obj)
assert a.has_shared_memory()
try:
a = self.array([2], intent.in_.inout, self.num2seq)
except TypeError as msg:
if not str(msg).startswith(
"failed to initialize intent(inout|inplace|cache) array"):
raise
else:
raise SystemError("intent(inout) should have failed on sequence")
def test_f_inout_23seq(self):
obj = np.array(self.num23seq, dtype=self.type.dtype, order="F")
shape = (len(self.num23seq), len(self.num23seq[0]))
a = self.array(shape, intent.in_.inout, obj)
assert a.has_shared_memory()
obj = np.array(self.num23seq, dtype=self.type.dtype, order="C")
try:
a = self.array(shape, intent.in_.inout, obj)
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(inout) array"):
raise
else:
raise SystemError(
"intent(inout) should have failed on improper array")
def test_c_inout_23seq(self):
obj = np.array(self.num23seq, dtype=self.type.dtype)
shape = (len(self.num23seq), len(self.num23seq[0]))
a = self.array(shape, intent.in_.c.inout, obj)
assert a.has_shared_memory()
def test_in_copy_from_2casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num2seq, dtype=t.dtype)
a = self.array([len(self.num2seq)], intent.in_.copy, obj)
assert not a.has_shared_memory()
def test_c_in_from_23seq(self):
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_,
self.num23seq)
assert not a.has_shared_memory()
def test_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_, obj)
assert not a.has_shared_memory()
def test_f_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype, order="F")
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory()
else:
assert not a.has_shared_memory()
def test_c_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.c, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory()
else:
assert not a.has_shared_memory()
def test_f_copy_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype, order="F")
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.copy,
obj)
assert not a.has_shared_memory()
def test_c_copy_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.c.copy,
obj)
assert not a.has_shared_memory()
def test_in_cache_from_2casttype(self):
for t in self.type.all_types():
if t.elsize != self.type.elsize:
continue
obj = np.array(self.num2seq, dtype=t.dtype)
shape = (len(self.num2seq), )
a = self.array(shape, intent.in_.c.cache, obj)
assert a.has_shared_memory()
a = self.array(shape, intent.in_.cache, obj)
assert a.has_shared_memory()
obj = np.array(self.num2seq, dtype=t.dtype, order="F")
a = self.array(shape, intent.in_.c.cache, obj)
assert a.has_shared_memory()
a = self.array(shape, intent.in_.cache, obj)
assert a.has_shared_memory(), repr(t.dtype)
try:
a = self.array(shape, intent.in_.cache, obj[::-1])
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(cache) array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on multisegmented array")
def test_in_cache_from_2casttype_failure(self):
for t in self.type.all_types():
if t.NAME == 'STRING':
continue
if t.elsize >= self.type.elsize:
continue
is_int = np.issubdtype(t.dtype, np.integer)
if is_int and int(self.num2seq[0]) > np.iinfo(t.dtype).max:
continue
obj = np.array(self.num2seq, dtype=t.dtype)
shape = (len(self.num2seq), )
try:
self.array(shape, intent.in_.cache, obj)
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(cache) array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on smaller array")
def test_cache_hidden(self):
shape = (2, )
a = self.array(shape, intent.cache.hide, None)
assert a.arr.shape == shape
shape = (2, 3)
a = self.array(shape, intent.cache.hide, None)
assert a.arr.shape == shape
shape = (-1, 3)
try:
a = self.array(shape, intent.cache.hide, None)
except ValueError as msg:
if not str(msg).startswith(
"failed to create intent(cache|hide)|optional array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on undefined dimensions")
def test_hidden(self):
shape = (2, )
a = self.array(shape, intent.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
shape = (2, 3)
a = self.array(shape, intent.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert a.arr.flags["FORTRAN"] and not a.arr.flags["CONTIGUOUS"]
shape = (2, 3)
a = self.array(shape, intent.c.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert not a.arr.flags["FORTRAN"] and a.arr.flags["CONTIGUOUS"]
shape = (-1, 3)
try:
a = self.array(shape, intent.hide, None)
except ValueError as msg:
if not str(msg).startswith(
"failed to create intent(cache|hide)|optional array"):
raise
else:
raise SystemError(
"intent(hide) should have failed on undefined dimensions")
def test_optional_none(self):
shape = (2, )
a = self.array(shape, intent.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
shape = (2, 3)
a = self.array(shape, intent.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert a.arr.flags["FORTRAN"] and not a.arr.flags["CONTIGUOUS"]
shape = (2, 3)
a = self.array(shape, intent.c.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert not a.arr.flags["FORTRAN"] and a.arr.flags["CONTIGUOUS"]
def test_optional_from_2seq(self):
obj = self.num2seq
shape = (len(obj), )
a = self.array(shape, intent.optional, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
def test_optional_from_23seq(self):
obj = self.num23seq
shape = (len(obj), len(obj[0]))
a = self.array(shape, intent.optional, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
a = self.array(shape, intent.optional.c, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
def test_inplace(self):
obj = np.array(self.num23seq, dtype=self.type.dtype)
assert not obj.flags["FORTRAN"] and obj.flags["CONTIGUOUS"]
shape = obj.shape
a = self.array(shape, intent.inplace, obj)
assert obj[1][2] == a.arr[1][2], repr((obj, a.arr))
a.arr[1][2] = 54
assert obj[1][2] == a.arr[1][2] == np.array(54, dtype=self.type.dtype)
assert a.arr is obj
assert obj.flags["FORTRAN"]
assert not obj.flags["CONTIGUOUS"]
def test_inplace_from_casttype(self):
for t in self.type.cast_types():
if t is self.type:
continue
obj = np.array(self.num23seq, dtype=t.dtype)
assert obj.dtype.type == t.type
assert obj.dtype.type is not self.type.type
assert not obj.flags["FORTRAN"] and obj.flags["CONTIGUOUS"]
shape = obj.shape
a = self.array(shape, intent.inplace, obj)
assert obj[1][2] == a.arr[1][2], repr((obj, a.arr))
a.arr[1][2] = 54
assert obj[1][2] == a.arr[1][2] == np.array(54, dtype=self.type.dtype)
assert a.arr is obj
assert obj.flags["FORTRAN"]
assert not obj.flags["CONTIGUOUS"]
assert obj.dtype.type is self.type.type
.\numpy\numpy\f2py\tests\test_assumed_shape.py
import os
import pytest
import tempfile
from . import util
class TestAssumedShapeSumExample(util.F2PyTest):
sources = [
util.getpath("tests", "src", "assumed_shape", "foo_free.f90"),
util.getpath("tests", "src", "assumed_shape", "foo_use.f90"),
util.getpath("tests", "src", "assumed_shape", "precision.f90"),
util.getpath("tests", "src", "assumed_shape", "foo_mod.f90"),
util.getpath("tests", "src", "assumed_shape", ".f2py_f2cmap"),
]
@pytest.mark.slow
def test_all(self):
r = self.module.fsum([1, 2])
assert r == 3
r = self.module.sum([1, 2])
assert r == 3
r = self.module.sum_with_use([1, 2])
assert r == 3
r = self.module.mod.sum([1, 2])
assert r == 3
r = self.module.mod.fsum([1, 2])
assert r == 3
class TestF2cmapOption(TestAssumedShapeSumExample):
def setup_method(self):
self.sources = list(self.sources)
f2cmap_src = self.sources.pop(-1)
self.f2cmap_file = tempfile.NamedTemporaryFile(delete=False)
with open(f2cmap_src, "rb") as f:
self.f2cmap_file.write(f.read())
self.f2cmap_file.close()
self.sources.append(self.f2cmap_file.name)
self.options = ["--f2cmap", self.f2cmap_file.name]
super().setup_method()
def teardown_method(self):
os.unlink(self.f2cmap_file.name)
.\numpy\numpy\f2py\tests\test_block_docstring.py
import sys
import pytest
from . import util
from numpy.testing import IS_PYPY
@pytest.mark.slow
class TestBlockDocString(util.F2PyTest):
sources = [util.getpath("tests", "src", "block_docstring", "foo.f")]
@pytest.mark.skipif(sys.platform == "win32",
reason="Fails with MinGW64 Gfortran (Issue #9673)")
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_block_docstring(self):
expected = "bar : 'i'-array(2,3)\n"
assert self.module.block.__doc__ == expected
.\numpy\numpy\f2py\tests\test_callback.py
import math
import textwrap
import sys
import pytest
import threading
import traceback
import time
import numpy as np
from numpy.testing import IS_PYPY
from . import util
class TestF77Callback(util.F2PyTest):
sources = [util.getpath("tests", "src", "callback", "foo.f")]
@pytest.mark.parametrize("name", "t,t2".split(","))
@pytest.mark.slow
def test_all(self, name):
self.check_function(name)
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_docstring(self):
expected = textwrap.dedent("""\
a = t(fun,[fun_extra_args])
Wrapper for ``t``.
Parameters
----------
fun : call-back function
Other Parameters
----------------
fun_extra_args : input tuple, optional
Default: ()
Returns
-------
a : int
Notes
-----
Call-back functions::
def fun(): return a
Return objects:
a : int
""")
assert self.module.t.__doc__ == expected
def check_function(self, name):
t = getattr(self.module, name)
r = t(lambda: 4)
assert r == 4
r = t(lambda a: 5, fun_extra_args=(6, ))
assert r == 5
r = t(lambda a: a, fun_extra_args=(6, ))
assert r == 6
r = t(lambda a: 5 + a, fun_extra_args=(7, ))
assert r == 12
r = t(lambda a: math.degrees(a), fun_extra_args=(math.pi, ))
assert r == 180
r = t(math.degrees, fun_extra_args=(math.pi, ))
assert r == 180
r = t(self.module.func, fun_extra_args=(6, ))
assert r == 17
r = t(self.module.func0)
assert r == 11
r = t(self.module.func0._cpointer)
assert r == 11
class A:
def __call__(self):
return 7
def mth(self):
return 9
a = A()
r = t(a)
assert r == 7
r = t(a.mth)
assert r == 9
@pytest.mark.skipif(sys.platform == 'win32',
reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback(self):
def callback(code):
if code == "r":
return 0
else:
return 1
f = getattr(self.module, "string_callback")
r = f(callback)
assert r == 0
@pytest.mark.skipif(sys.platform == 'win32',
reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback_array(self):
cu1 = np.zeros((1, ), "S8")
cu2 = np.zeros((1, 8), "c")
cu3 = np.array([""], "S8")
def callback(cu, lencu):
if cu.shape != (lencu,):
return 1
if cu.dtype != "S8":
return 2
if not np.all(cu == b""):
return 3
return 0
f = getattr(self.module, "string_callback_array")
for cu in [cu1, cu2, cu3]:
res = f(callback, cu, cu.size)
assert res == 0
def test_threadsafety(self):
errors = []
def cb():
time.sleep(1e-3)
r = self.module.t(lambda: 123)
assert r == 123
return 42
def runner(name):
try:
for j in range(50):
r = self.module.t(cb)
assert r == 42
self.check_function(name)
except Exception:
errors.append(traceback.format_exc())
threads = [
threading.Thread(target=runner, args=(arg, ))
for arg in ("t", "t2") for n in range(20)
]
for t in threads:
t.start()
for t in threads:
t.join()
errors = "\n\n".join(errors)
if errors:
raise AssertionError(errors)
def test_hidden_callback(self):
try:
self.module.hidden_callback(2)
except Exception as msg:
assert str(msg).startswith("Callback global_f not defined")
try:
self.module.hidden_callback2(2)
except Exception as msg:
assert str(msg).startswith("cb: Callback global_f not defined")
self.module.global_f = lambda x: x + 1
r = self.module.hidden_callback(2)
assert r == 3
self.module.global_f = lambda x: x + 2
r = self.module.hidden_callback(2)
assert r == 4
del self.module.global_f
try:
self.module.hidden_callback(2)
except Exception as msg:
assert str(msg).startswith("Callback global_f not defined")
self.module.global_f = lambda x=0: x + 3
r = self.module.hidden_callback(2)
assert r == 5
r = self.module.hidden_callback2(2)
assert r == 3
class TestF77CallbackPythonTLS(TestF77Callback):
"""
Callback tests using Python thread-local storage instead of
compiler-provided
"""
options = ["-DF2PY_USE_PYTHON_TLS"]
class TestF90Callback(util.F2PyTest):
sources = [util.getpath("tests", "src", "callback", "gh17797.f90")]
@pytest.mark.slow
def test_gh17797(self):
def incr(x):
return x + 123
y = np.array([1, 2, 3], dtype=np.int64)
r = self.module.gh17797(incr, y)
assert r == 123 + 1 + 2 + 3
class TestGH18335(util.F2PyTest):
"""
The reproduction of the reported issue requires specific input that
extensions may break the issue conditions, so the reproducer is
implemented as a separate test class. Do not extend this test with
other tests!
"""
sources = [util.getpath("tests", "src", "callback", "gh18335.f90")]
@pytest.mark.slow
def test_gh18335(self):
def foo(x):
x[0] += 1
r = self.module.gh18335(foo)
assert r == 123 + 1
class TestGH25211(util.F2PyTest):
sources = [util.getpath("tests", "src", "callback", "gh25211.f"),
util.getpath("tests", "src", "callback", "gh25211.pyf")]
module_name = "callback2"
def test_gh25211(self):
def bar(x):
return x*x
res = self.module.foo(bar)
assert res == 110
.\numpy\numpy\f2py\tests\test_character.py
import pytest
import textwrap
from numpy.testing import assert_array_equal, assert_equal, assert_raises
import numpy as np
from numpy.f2py.tests import util
@pytest.mark.slow
class TestCharacterString(util.F2PyTest):
suffix = '.f90'
fprefix = 'test_character_string'
length_list = ['1', '3', 'star']
code = ''
for length in length_list:
fsuffix = length
clength = dict(star='(*)').get(length, length)
code += textwrap.dedent(f"""
subroutine {fprefix}_input_{fsuffix}(c, o, n)
character*{clength}, intent(in) :: c
integer n
!f2py integer, depend(c), intent(hide) :: n = slen(c)
integer*1, dimension(n) :: o
!f2py intent(out) o
o = transfer(c, o)
end subroutine {fprefix}_input_{fsuffix}
subroutine {fprefix}_output_{fsuffix}(c, o, n)
character*{clength}, intent(out) :: c
integer n
integer*1, dimension(n), intent(in) :: o
!f2py integer, depend(o), intent(hide) :: n = len(o)
c = transfer(o, c)
end subroutine {fprefix}_output_{fsuffix}
subroutine {fprefix}_array_input_{fsuffix}(c, o, m, n)
integer m, i, n
character*{clength}, intent(in), dimension(m) :: c
!f2py integer, depend(c), intent(hide) :: m = len(c)
!f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c)
integer*1, dimension(m, n), intent(out) :: o
do i=1,m
o(i, :) = transfer(c(i), o(i, :))
end do
end subroutine {fprefix}_array_input_{fsuffix}
subroutine {fprefix}_array_output_{fsuffix}(c, o, m, n)
character*{clength}, intent(out), dimension(m) :: c
integer n
integer*1, dimension(m, n), intent(in) :: o
!f2py character(f2py_len=n) :: c
!f2py integer, depend(o), intent(hide) :: m = len(o)
!f2py integer, depend(o), intent(hide) :: n = shape(o, 1)
do i=1,m
c(i) = transfer(o(i, :), c(i))
end do
end subroutine {fprefix}_array_output_{fsuffix}
subroutine {fprefix}_2d_array_input_{fsuffix}(c, o, m1, m2, n)
integer m1, m2, i, j, n
character*{clength}, intent(in), dimension(m1, m2) :: c
!f2py integer, depend(c), intent(hide) :: m1 = len(c)
!f2py integer, depend(c), intent(hide) :: m2 = shape(c, 1)
!f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c)
integer*1, dimension(m1, m2, n), intent(out) :: o
do i=1,m1
do j=1,m2
o(i, j, :) = transfer(c(i, j), o(i, j, :))
end do
end do
end subroutine {fprefix}_2d_array_input_{fsuffix}
""")
@pytest.mark.parametrize("length", length_list)
def test_input(self, length):
fsuffix = {'(*)': 'star'}.get(length, length)
f = getattr(self.module, self.fprefix + '_input_' + fsuffix)
a = {'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length]
assert_array_equal(f(a), np.array(list(map(ord, a)), dtype='u1'))
@pytest.mark.parametrize("length", length_list[:-1])
def test_output(self, length):
fsuffix = length
f = getattr(self.module, self.fprefix + '_output_' + fsuffix)
a = {'1': 'a', '3': 'abc'}[length]
assert_array_equal(f(np.array(list(map(ord, a)), dtype='u1')),
a.encode())
@pytest.mark.parametrize("length", length_list)
def test_array_input(self, length):
fsuffix = length
f = getattr(self.module, self.fprefix + '_array_input_' + fsuffix)
a = np.array([{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
{'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length],
], dtype='S')
expected = np.array([[c for c in s] for s in a], dtype='u1')
assert_array_equal(f(a), expected)
@pytest.mark.parametrize("length", length_list)
def test_array_output(self, length):
fsuffix = length
f = getattr(self.module, self.fprefix + '_array_output_' + fsuffix)
expected = np.array(
[{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
{'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]], dtype='S')
a = np.array([[c for c in s] for s in expected], dtype='u1')
assert_array_equal(f(a), expected)
@pytest.mark.parametrize("length", length_list)
def test_2d_array_input(self, length):
fsuffix = length
f = getattr(self.module, self.fprefix + '_2d_array_input_' + fsuffix)
a = np.array([[{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
{'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]],
[{'1': 'f', '3': 'fgh', 'star': 'fghij' * 3}[length],
{'1': 'F', '3': 'FGH', 'star': 'FGHIJ' * 3}[length]]],
dtype='S')
expected = np.array([[[c for c in item] for item in row] for row in a],
dtype='u1', order='F')
assert_array_equal(f(a), expected)
class TestCharacter(util.F2PyTest):
suffix = '.f90'
fprefix = 'test_character'
code = textwrap.dedent(f"""
subroutine {fprefix}_input(c, o)
character, intent(in) :: c
integer*1 o
!f2py intent(out) o
o = transfer(c, o)
end subroutine {fprefix}_input
subroutine {fprefix}_output(c, o)
character :: c
integer*1, intent(in) :: o
!f2py intent(out) c
c = transfer(o, c)
end subroutine {fprefix}_output
subroutine {fprefix}_input_output(c, o)
character, intent(in) :: c
character o
!f2py intent(out) o
o = c
end subroutine {fprefix}_input_output
subroutine {fprefix}_inout(c, n)
character :: c, n
!f2py intent(in) n
!f2py intent(inout) c
c = n
end subroutine {fprefix}_inout
function {fprefix}_return(o) result (c)
character :: c
character, intent(in) :: o
c = transfer(o, c)
end function {fprefix}_return
subroutine {fprefix}_array_input(c, o)
character, intent(in) :: c(3)
integer*1 o(3)
!f2py intent(out) o
integer i
do i=1,3
o(i) = transfer(c(i), o(i))
end do
end subroutine {fprefix}_array_input
subroutine {fprefix}_2d_array_input(c, o)
character, intent(in) :: c(2, 3)
integer*1 o(2, 3)
!f2py intent(out) o
integer i, j
do i=1,2
do j=1,3
o(i, j) = transfer(c(i, j), o(i, j))
end do
end do
end subroutine {fprefix}_2d_array_input
subroutine {fprefix}_array_output(c, o)
character :: c(3)
integer*1, intent(in) :: o(3)
!f2py intent(out) c
do i=1,3
c(i) = transfer(o(i), c(i))
end do
end subroutine {fprefix}_array_output
subroutine {fprefix}_array_inout(c, n)
character :: c(3), n(3)
!f2py intent(in) n(3)
!f2py intent(inout) c(3)
do i=1,3
c(i) = n(i)
end do
end subroutine {fprefix}_array_inout
subroutine {fprefix}_2d_array_inout(c, n)
character :: c(2, 3), n(2, 3)
!f2py intent(in) n(2, 3)
!f2py intent(inout) c(2, 3)
integer i, j
do i=1,2
do j=1,3
c(i, j) = n(i, j)
end do
end do
end subroutine {fprefix}_2d_array_inout
function {fprefix}_array_return(o) result (c)
character, dimension(3) :: c
character, intent(in) :: o(3)
do i=1,3
c(i) = o(i)
end do
end function {fprefix}_array_return
function {fprefix}_optional(o) result (c)
character, intent(in) :: o
!f2py character o = "a"
character :: c
c = o
end function {fprefix}_optional
""")
@pytest.mark.parametrize("dtype", ['c', 'S1'])
def test_input(self, dtype):
f = getattr(self.module, self.fprefix + '_input')
assert_equal(f(np.array('a', dtype=dtype)), ord('a'))
assert_equal(f(np.array(b'a', dtype=dtype)), ord('a'))
assert_equal(f(np.array(['a'], dtype=dtype)), ord('a'))
assert_equal(f(np.array('abc', dtype=dtype)), ord('a'))
assert_equal(f(np.array([['a']], dtype=dtype)), ord('a'))
def test_input_varia(self):
f = getattr(self.module, self.fprefix + '_input')
assert_equal(f('a'), ord('a'))
assert_equal(f(b'a'), ord(b'a'))
assert_equal(f(''), 0)
assert_equal(f(b''), 0)
assert_equal(f(b'\0'), 0)
assert_equal(f('ab'), ord('a'))
assert_equal(f(b'ab'), ord('a'))
assert_equal(f(['a']), ord('a'))
assert_equal(f(np.array(b'a')), ord('a'))
assert_equal(f(np.array([b'a'])), ord('a'))
a = np.array('a')
assert_equal(f(a), ord('a'))
a = np.array(['a'])
assert_equal(f(a), ord('a'))
try:
f([])
except IndexError as msg:
if not str(msg).endswith(' got 0-list'):
raise
else:
raise SystemError(f'{f.__name__} should have failed on empty list')
try:
f(97)
except TypeError as msg:
if not str(msg).endswith(' got int instance'):
raise
else:
raise SystemError(f'{f.__name__} should have failed on int value')
@pytest.mark.parametrize("dtype", ['c', 'S1', 'U1'])
def test_array_input(self, dtype):
f = getattr(self.module, self.fprefix + '_array_input')
assert_array_equal(f(np.array(['a', 'b', 'c'], dtype=dtype)),
np.array(list(map(ord, 'abc')), dtype='i1'))
assert_array_equal(f(np.array([b'a', b'b', b'c'], dtype=dtype)),
np.array(list(map(ord, 'abc')), dtype='i1'))
def test_array_input_varia(self):
f = getattr(self.module, self.fprefix + '_array_input')
assert_array_equal(f(['a', 'b', 'c']),
np.array(list(map(ord, 'abc')), dtype='i1'))
assert_array_equal(f([b'a', b'b', b'c']),
np.array(list(map(ord, 'abc')), dtype='i1'))
try:
f(['a', 'b', 'c', 'd'])
except ValueError as msg:
if not str(msg).endswith(
'th dimension must be fixed to 3 but got 4'):
raise
else:
raise SystemError(
f'{f.__name__} should have failed on wrong input')
@pytest.mark.parametrize("dtype", ['c', 'S1', 'U1'])
def test_2d_array_input(self, dtype):
f = getattr(self.module, self.fprefix + '_2d_array_input')
a = np.array([['a', 'b', 'c'],
['d', 'e', 'f']], dtype=dtype, order='F')
expected = a.view(np.uint32 if dtype == 'U1' else np.uint8)
assert_array_equal(f(a), expected)
def test_output(self):
f = getattr(self.module, self.fprefix + '_output')
assert_equal(f(ord(b'a')), b'a')
assert_equal(f(0), b'\0')
def test_array_output(self):
f = getattr(self.module, self.fprefix + '_array_output')
assert_array_equal(f(list(map(ord, 'abc'))),
np.array(list('abc'), dtype='S1'))
def test_input_output(self):
f = getattr(self.module, self.fprefix + '_input_output')
assert_equal(f(b'a'), b'a')
assert_equal(f('a'), b'a')
assert_equal(f(''), b'\0')
@pytest.mark.parametrize("dtype", ['c', 'S1'])
def test_inout(self, dtype):
f = getattr(self.module, self.fprefix + '_inout')
a = np.array(list('abc'), dtype=dtype)
f(a, 'A')
assert_array_equal(a, np.array(list('Abc'), dtype=a.dtype))
f(a[1:], 'B')
assert_array_equal(a, np.array(list('ABc'), dtype=a.dtype))
a = np.array(['abc'], dtype=dtype)
f(a, 'A')
assert_array_equal(a, np.array(['Abc'], dtype=a.dtype))
def test_inout_varia(self):
f = getattr(self.module, self.fprefix + '_inout')
a = np.array('abc', dtype='S3')
f(a, 'A')
assert_array_equal(a, np.array('Abc', dtype=a.dtype))
a = np.array(['abc'], dtype='S3')
f(a, 'A')
assert_array_equal(a, np.array(['Abc'], dtype=a.dtype))
try:
f('abc', 'A')
except ValueError as msg:
if not str(msg).endswith(' got 3-str'):
raise
else:
raise SystemError(f'{f.__name__} should have failed on str value')
@pytest.mark.parametrize("dtype", ['c', 'S1'])
def test_array_inout(self, dtype):
f = getattr(self.module, self.fprefix + '_array_inout')
n = np.array(['A', 'B', 'C'], dtype=dtype, order='F')
a = np.array(['a', 'b', 'c'], dtype=dtype, order='F')
f(a, n)
assert_array_equal(a, n)
a = np.array(['a', 'b', 'c', 'd'], dtype=dtype)
f(a[1:], n)
assert_array_equal(a, np.array(['a', 'A', 'B', 'C'], dtype=dtype))
a = np.array([['a', 'b', 'c']], dtype=dtype, order='F')
f(a, n)
assert_array_equal(a, np.array([['A', 'B', 'C']], dtype=dtype))
a = np.array(['a', 'b', 'c', 'd'], dtype=dtype, order='F')
try:
f(a, n)
except ValueError as msg:
if not str(msg).endswith(
'th dimension must be fixed to 3 but got 4'):
raise
else:
raise SystemError(
f'{f.__name__} should have failed on wrong input')
@pytest.mark.parametrize("dtype", ['c', 'S1'])
def test_2d_array_inout(self, dtype):
f = getattr(self.module, self.fprefix + '_2d_array_inout')
n = np.array([['A', 'B', 'C'],
['D', 'E', 'F']],
dtype=dtype, order='F')
a = np.array([['a', 'b', 'c'],
['d', 'e', 'f']],
dtype=dtype, order='F')
f(a, n)
assert_array_equal(a, n)
def test_return(self):
f = getattr(self.module, self.fprefix + '_return')
assert_equal(f('a'), b'a')
@pytest.mark.skip('fortran function returning array segfaults')
def test_array_return(self):
f = getattr(self.module, self.fprefix + '_array_return')
a = np.array(list('abc'), dtype='S1')
assert_array_equal(f(a), a)
def test_optional(self):
f = getattr(self.module, self.fprefix + '_optional')
assert_equal(f(), b"a")
assert_equal(f(b'B'), b"B")
class TestMiscCharacter(util.F2PyTest):
suffix = '.f90'
fprefix = 'test_misc_character'
code = textwrap.dedent(f"""
subroutine {fprefix}_gh18684(x, y, m)
character(len=5), dimension(m), intent(in) :: x
character*5, dimension(m), intent(out) :: y
integer i, m
!f2py integer, intent(hide), depend(x) :: m = f2py_len(x)
# 这是一个F2Py的特殊注释,指定了一个隐藏的整数参数m,依赖于x的长度
do i=1,m
y(i) = x(i)
end do
end subroutine {fprefix}_gh18684
subroutine {fprefix}_gh6308(x, i)
integer i
!f2py check(i>=0 && i<12) i
# 检查i的值是否在0到11之间
character*5 name, x
common name(12)
name(i + 1) = x
# 将x的值存入name数组的第i+1个位置
end subroutine {fprefix}_gh6308
subroutine {fprefix}_gh4519(x)
character(len=*), intent(in) :: x(:)
!f2py intent(out) x
integer :: i
! Uncomment for debug printing:
!do i=1, size(x)
! print*, "x(",i,")=", x(i)
!end do
# 子程序用于处理输入参数x,但在此没有具体实现,注释掉了用于调试的打印代码
end subroutine {fprefix}_gh4519
pure function {fprefix}_gh3425(x) result (y)
character(len=*), intent(in) :: x
character(len=len(x)) :: y
integer :: i
do i = 1, len(x)
j = iachar(x(i:i))
if (j>=iachar("a") .and. j<=iachar("z") ) then
y(i:i) = achar(j-32)
else
y(i:i) = x(i:i)
endif
end do
# 纯函数,将输入字符串x中的小写字母转换为大写,返回结果y
end function {fprefix}_gh3425
subroutine {fprefix}_character_bc_new(x, y, z)
character, intent(in) :: x
character, intent(out) :: y
!f2py character, depend(x) :: y = x
!f2py character, dimension((x=='a'?1:2)), depend(x), intent(out) :: z
character, dimension(*) :: z
!f2py character, optional, check(x == 'a' || x == 'b') :: x = 'a'
!f2py callstatement (*f2py_func)(&x, &y, z)
!f2py callprotoargument character*, character*, character*
if (y.eq.x) then
y = x
else
y = 'e'
endif
z(1) = 'c'
# 子程序,根据输入的x,将y设为x或者'e',z数组的第一个元素设为'c'
end subroutine {fprefix}_character_bc_new
subroutine {fprefix}_character_bc_old(x, y, z)
character, intent(in) :: x
character, intent(out) :: y
!f2py character, depend(x) :: y = x[0]
!f2py character, dimension((*x=='a'?1:2)), depend(x), intent(out) :: z
character, dimension(*) :: z
!f2py character, optional, check(*x == 'a' || x[0] == 'b') :: x = 'a'
!f2py callstatement (*f2py_func)(x, y, z)
!f2py callprotoargument char*, char*, char*
if (y.eq.x) then
y = x
else
y = 'e'
endif
z(1) = 'c'
# 子程序,根据输入的x,将y设为x或者'e',z数组的第一个元素设为'c'
end subroutine {fprefix}_character_bc_old
""")
@pytest.mark.slow
def test_gh18684(self):
f = getattr(self.module, self.fprefix + '_gh18684')
x = np.array(["abcde", "fghij"], dtype='S5')
y = f(x)
assert_array_equal(x, y)
def test_gh6308(self):
f = getattr(self.module, self.fprefix + '_gh6308')
assert_equal(self.module._BLNK_.name.dtype, np.dtype('S5'))
assert_equal(len(self.module._BLNK_.name), 12)
f("abcde", 0)
assert_equal(self.module._BLNK_.name[0], b"abcde")
f("12345", 5)
assert_equal(self.module._BLNK_.name[5], b"12345")
def test_gh4519(self):
f = getattr(self.module, self.fprefix + '_gh4519')
for x, expected in [
('a', dict(shape=(), dtype=np.dtype('S1'))),
('text', dict(shape=(), dtype=np.dtype('S4'))),
(np.array(['1', '2', '3'], dtype='S1'),
dict(shape=(3,), dtype=np.dtype('S1'))),
(['1', '2', '34'],
dict(shape=(3,), dtype=np.dtype('S2'))),
(['', ''], dict(shape=(2,), dtype=np.dtype('S1')))]:
r = f(x)
for k, v in expected.items():
assert_equal(getattr(r, k), v)
def test_gh3425(self):
f = getattr(self.module, self.fprefix + '_gh3425')
assert_equal(f('abC'), b'ABC')
assert_equal(f(''), b'')
assert_equal(f('abC12d'), b'ABC12D')
@pytest.mark.parametrize("state", ['new', 'old'])
def test_character_bc(self, state):
f = getattr(self.module, self.fprefix + '_character_bc_' + state)
c, a = f()
assert_equal(c, b'a')
assert_equal(len(a), 1)
c, a = f(b'b')
assert_equal(c, b'b')
assert_equal(len(a), 2)
assert_raises(Exception, lambda: f(b'c'))
class TestStringScalarArr(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "scalar_string.f90")]
def test_char(self):
for out in (self.module.string_test.string,
self.module.string_test.string77):
expected = ()
assert out.shape == expected
expected = '|S8'
assert out.dtype == expected
def test_char_arr(self):
for out in (self.module.string_test.strarr,
self.module.string_test.strarr77):
expected = (5, 7)
assert out.shape == expected
expected = '|S12'
assert out.dtype == expected
class TestStringAssumedLength(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "gh24008.f")]
def test_gh24008(self):
self.module.greet("joe", "bob")
@pytest.mark.slow
class TestStringOptionalInOut(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "gh24662.f90")]
def test_gh24662(self):
self.module.string_inout_optional()
a = np.array('hi', dtype='S32')
self.module.string_inout_optional(a)
assert "output string" in a.tobytes().decode()
with pytest.raises(Exception):
aa = "Hi"
self.module.string_inout_optional(aa)
@pytest.mark.slow
class TestNewCharHandling(util.F2PyTest):
sources = [
util.getpath("tests", "src", "string", "gh25286.pyf"),
util.getpath("tests", "src", "string", "gh25286.f90")
]
module_name = "_char_handling_test"
def test_gh25286(self):
info = self.module.charint('T')
assert info == 2
@pytest.mark.slow
class TestBCCharHandling(util.F2PyTest):
sources = [
util.getpath("tests", "src", "string", "gh25286_bc.pyf"),
util.getpath("tests", "src", "string", "gh25286.f90")
]
module_name = "_char_handling_test"
def test_gh25286(self):
info = self.module.charint('T')
assert info == 2
.\numpy\numpy\f2py\tests\test_common.py
import pytest
import numpy as np
from . import util
@pytest.mark.slow
class TestCommonBlock(util.F2PyTest):
sources = [util.getpath("tests", "src", "common", "block.f")]
def test_common_block(self):
self.module.initcb()
assert self.module.block.long_bn == np.array(1.0, dtype=np.float64)
assert self.module.block.string_bn == np.array("2", dtype="|S1")
assert self.module.block.ok == np.array(3, dtype=np.int32)
class TestCommonWithUse(util.F2PyTest):
sources = [util.getpath("tests", "src", "common", "gh19161.f90")]
def test_common_gh19161(self):
assert self.module.data.x == 0
.\numpy\numpy\f2py\tests\test_crackfortran.py
import importlib
import codecs
import time
import unicodedata
import pytest
import numpy as np
from numpy.f2py.crackfortran import markinnerspaces, nameargspattern
from . import util
from numpy.f2py import crackfortran
import textwrap
import contextlib
import io
class TestNoSpace(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "gh15035.f")]
def test_module(self):
k = np.array([1, 2, 3], dtype=np.float64)
w = np.array([1, 2, 3], dtype=np.float64)
self.module.subb(k)
assert np.allclose(k, w + 1)
self.module.subc([w, k])
assert np.allclose(k, w + 1)
assert self.module.t0("23") == b"2"
class TestPublicPrivate:
def test_defaultPrivate(self):
fpath = util.getpath("tests", "src", "crackfortran", "privatemod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "private" in mod["vars"]["a"]["attrspec"]
assert "public" not in mod["vars"]["a"]["attrspec"]
assert "private" in mod["vars"]["b"]["attrspec"]
assert "public" not in mod["vars"]["b"]["attrspec"]
assert "private" not in mod["vars"]["seta"]["attrspec"]
assert "public" in mod["vars"]["seta"]["attrspec"]
def test_defaultPublic(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "publicmod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "private" in mod["vars"]["a"]["attrspec"]
assert "public" not in mod["vars"]["a"]["attrspec"]
assert "private" not in mod["vars"]["seta"]["attrspec"]
assert "public" in mod["vars"]["seta"]["attrspec"]
def test_access_type(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "accesstype.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
tt = mod[0]['vars']
assert set(tt['a']['attrspec']) == {'private', 'bind(c)'}
assert set(tt['b_']['attrspec']) == {'public', 'bind(c)'}
assert set(tt['c']['attrspec']) == {'public'}
def test_nowrap_private_proceedures(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "gh23879.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
pyf = crackfortran.crack2fortran(mod)
assert 'bar' not in pyf
class TestModuleProcedure():
def test_moduleOperators(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "operators.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "body" in mod and len(mod["body"]) == 9
assert mod["body"][1]["name"] == "operator(.item.)"
assert "implementedby" in mod["body"][1]
assert mod["body"][1]["implementedby"] == ["item_int", "item_real"]
assert mod["body"][2]["name"] == "operator(==)"
assert "implementedby" in mod["body"][2]
assert mod["body"][2]["implementedby"] == ["items_are_equal"]
assert mod["body"][3]["name"] == "assignment(=)"
assert "implementedby" in mod["body"][3]
def test_notPublicPrivate(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "pubprivmod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert mod['vars']['a']['attrspec'] == ['private', ]
assert mod['vars']['b']['attrspec'] == ['public', ]
assert mod['vars']['seta']['attrspec'] == ['public', ]
class TestExternal(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "gh17859.f")]
def test_external_as_statement(self):
def incr(x):
return x + 123
r = self.module.external_as_statement(incr)
assert r == 123
def test_external_as_attribute(self):
def incr(x):
return x + 123
r = self.module.external_as_attribute(incr)
assert r == 123
class TestCrackFortran(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "gh2848.f90")]
def test_gh2848(self):
r = self.module.gh2848(1, 2)
assert r == (1, 2)
class TestMarkinnerspaces:
def test_do_not_touch_normal_spaces(self):
test_list = ["a ", " a", "a b c", "'abcdefghij'"]
for i in test_list:
assert markinnerspaces(i) == i
def test_one_relevant_space(self):
assert markinnerspaces("a 'b c' \\' \\'") == "a 'b@_@c' \\' \\'"
assert markinnerspaces(r'a "b c" \" \"') == r'a "b@_@c" \" \"'
def test_ignore_inner_quotes(self):
assert markinnerspaces("a 'b c\" \" d' e") == "a 'b@_@c\"@_@\"@_@d' e"
assert markinnerspaces("a \"b c' ' d\" e") == "a \"b@_@c'@_@'@_@d\" e"
def test_multiple_relevant_spaces(self):
assert markinnerspaces("a 'b c' 'd e'") == "a 'b@_@c' 'd@_@e'"
assert markinnerspaces(r'a "b c" "d e"') == r'a "b@_@c" "d@_@e"'
class TestDimSpec(util.F2PyTest):
"""This test suite tests various expressions that are used as dimension
specifications.
There exists two usage cases where analyzing dimensions
specifications are important.
In the first case, the size of output arrays must be defined based
on the inputs to a Fortran function. Because Fortran supports
arbitrary bases for indexing, for instance, `arr(lower:upper)`,
f2py has to evaluate an expression `upper - lower + 1` where
`lower` and `upper` are arbitrary expressions of input parameters.
The evaluation is performed in C, so f2py has to translate Fortran
expressions to valid C expressions (an alternative approach is
that a developer specifies the corresponding C expressions in a
.pyf file).
In the second case, when user provides an input array with a given
size but some hidden parameters used in dimensions specifications
need to be determined based on the input array size. This is a
harder problem because f2py has to solve the inverse problem: find
a parameter `p` such that `upper(p) - lower(p) + 1` equals to the
size of input array. In the case when this equation cannot be
solved (e.g. because the input array size is wrong), raise an
error before calling the Fortran function (that otherwise would
"""
suffix = ".f90"
code_template = textwrap.dedent("""
function get_arr_size_{count}(a, n) result (length)
integer, intent(in) :: n
integer, dimension({dimspec}), intent(out) :: a
integer length
length = size(a)
end function
subroutine get_inv_arr_size_{count}(a, n)
integer :: n
! the value of n is computed in f2py wrapper
!f2py intent(out) n
integer, dimension({dimspec}), intent(in) :: a
if (a({first}).gt.0) then
! print*, "a=", a
endif
end subroutine
""")
linear_dimspecs = [
"n", "2*n", "2:n", "n/2", "5 - n/2", "3*n:20", "n*(n+1):n*(n+5)",
"2*n, n"
]
nonlinear_dimspecs = ["2*n:3*n*n+2*n"]
all_dimspecs = linear_dimspecs + nonlinear_dimspecs
code = ""
for count, dimspec in enumerate(all_dimspecs):
lst = [(d.split(":")[0] if ":" in d else "1") for d in dimspec.split(',')]
code += code_template.format(
count=count,
dimspec=dimspec,
first=", ".join(lst),
)
@pytest.mark.parametrize("dimspec", all_dimspecs)
@pytest.mark.slow
def test_array_size(self, dimspec):
count = self.all_dimspecs.index(dimspec)
get_arr_size = getattr(self.module, f"get_arr_size_{count}")
for n in [1, 2, 3, 4, 5]:
sz, a = get_arr_size(n)
assert a.size == sz
@pytest.mark.parametrize("dimspec", all_dimspecs)
def test_inv_array_size(self, dimspec):
count = self.all_dimspecs.index(dimspec)
get_arr_size = getattr(self.module, f"get_arr_size_{count}")
get_inv_arr_size = getattr(self.module, f"get_inv_arr_size_{count}")
for n in [1, 2, 3, 4, 5]:
sz, a = get_arr_size(n)
if dimspec in self.nonlinear_dimspecs:
n1 = get_inv_arr_size(a, n)
else:
n1 = get_inv_arr_size(a)
sz1, _ = get_arr_size(n1)
assert sz == sz1, (n, n1, sz, sz1)
class TestModuleDeclaration:
def test_dependencies(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "foo_deps.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
assert mod[0]["vars"]["abar"]["="] == "bar('abar')"
class TestEval(util.F2PyTest):
def test_eval_scalar(self):
eval_scalar = crackfortran._eval_scalar
assert eval_scalar('123', {}) == '123'
assert eval_scalar('12 + 3', {}) == '15'
assert eval_scalar('a + b', dict(a=1, b=2)) == '3'
assert eval_scalar('"123"', {}) == "'123'"
class TestFortranReader(util.F2PyTest):
@pytest.mark.parametrize("encoding",
['ascii', 'utf-8', 'utf-16', 'utf-32'])
def test_input_encoding(self, tmp_path, encoding):
f_path = tmp_path / f"input_with_{encoding}_encoding.f90"
with f_path.open('w', encoding=encoding) as ff:
ff.write("""
subroutine foo()
end subroutine foo
""")
mod = crackfortran.crackfortran([str(f_path)])
assert mod[0]['name'] == 'foo'
@pytest.mark.slow
class TestUnicodeComment(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "unicode_comment.f90")]
@pytest.mark.skipif(
(importlib.util.find_spec("charset_normalizer") is None),
reason="test requires charset_normalizer which is not installed",
)
def test_encoding_comment(self):
self.module.foo(3)
class TestNameArgsPatternBacktracking:
@pytest.mark.parametrize(
['adversary'],
[
('@)@bind@(@',),
('@)@bind @(@',),
('@)@bind foo bar baz@(@',)
]
)
def test_nameargspattern_backtracking(self, adversary):
'''address ReDOS vulnerability:
https://github.com/numpy/numpy/issues/23338'''
trials_per_batch = 12
batches_per_regex = 4
start_reps, end_reps = 15, 25
for ii in range(start_reps, end_reps):
repeated_adversary = adversary * ii
for _ in range(batches_per_regex):
times = []
for _ in range(trials_per_batch):
t0 = time.perf_counter()
mtch = nameargspattern.search(repeated_adversary)
times.append(time.perf_counter() - t0)
assert np.median(times) < 0.2
assert not mtch
good_version_of_adversary = repeated_adversary + '@)@'
assert nameargspattern.search(good_version_of_adversary)
class TestFunctionReturn(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "gh23598.f90")]
@pytest.mark.slow
def test_function_rettype(self):
assert self.module.intproduct(3, 4) == 12
class TestFortranGroupCounters(util.F2PyTest):
def test_end_if_comment(self):
fpath = util.getpath("tests", "src", "crackfortran", "gh23533.f")
try:
crackfortran.crackfortran([str(fpath)])
except Exception as exc:
assert False, f"'crackfortran.crackfortran' raised an exception {exc}"
class TestF77CommonBlockReader():
def test_gh22648(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "gh22648.pyf")
with contextlib.redirect_stdout(io.StringIO()) as stdout_f2py:
mod = crackfortran.crackfortran([str(fpath)])
assert "Mismatch" not in stdout_f2py.getvalue()
class TestParamEval():
def test_param_eval_nested(self):
v = '(/3.14, 4./)'
g_params = dict(kind=crackfortran._kind_func,
selected_int_kind=crackfortran._selected_int_kind_func,
selected_real_kind=crackfortran._selected_real_kind_func)
params = {'dp': 8, 'intparamarray': {1: 3, 2: 5},
'nested': {1: 1, 2: 2, 3: 3}}
dimspec = '(2)'
ret = crackfortran.param_eval(v, g_params, params, dimspec=dimspec)
assert ret == {1: 3.14, 2: 4.0}
def test_param_eval_nonstandard_range(self):
v = '(/ 6, 3, 1 /)'
g_params = dict(kind=crackfortran._kind_func,
selected_int_kind=crackfortran._selected_int_kind_func,
selected_real_kind=crackfortran._selected_real_kind_func)
params = {}
dimspec = '(-1:1)'
ret = crackfortran.param_eval(v, g_params, params, dimspec=dimspec)
assert ret == {-1: 6, 0: 3, 1: 1}
def test_param_eval_empty_range(self):
v = '6'
g_params = dict(kind=crackfortran._kind_func,
selected_int_kind=crackfortran._selected_int_kind_func,
selected_real_kind=crackfortran._selected_real_kind_func)
params = {}
dimspec = ''
pytest.raises(ValueError, crackfortran.param_eval, v, g_params, params,
dimspec=dimspec)
def test_param_eval_non_array_param(self):
v = '3.14_dp'
g_params = dict(kind=crackfortran._kind_func,
selected_int_kind=crackfortran._selected_int_kind_func,
selected_real_kind=crackfortran._selected_real_kind_func)
params = {}
ret = crackfortran.param_eval(v, g_params, params, dimspec=None)
assert ret == '3.14_dp'
def test_param_eval_too_many_dims(self):
v = 'reshape((/ (i, i=1, 250) /), (/5, 10, 5/))'
g_params = dict(kind=crackfortran._kind_func,
selected_int_kind=crackfortran._selected_int_kind_func,
selected_real_kind=crackfortran._selected_real_kind_func)
params = {}
dimspec = '(0:4, 3:12, 5)'
pytest.raises(ValueError, crackfortran.param_eval, v, g_params, params,
dimspec=dimspec)
.\numpy\numpy\f2py\tests\test_data.py
import os
import pytest
import numpy as np
from . import util
from numpy.f2py.crackfortran import crackfortran
class TestData(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "data_stmts.f90")]
@pytest.mark.slow
def test_data_stmts(self):
assert self.module.cmplxdat.i == 2
assert self.module.cmplxdat.j == 3
assert self.module.cmplxdat.x == 1.5
assert self.module.cmplxdat.y == 2.0
assert self.module.cmplxdat.pi == 3.1415926535897932384626433832795028841971693993751058209749445923078164062
assert self.module.cmplxdat.medium_ref_index == np.array(1.+0.j)
assert np.all(self.module.cmplxdat.z == np.array([3.5, 7.0]))
assert np.all(self.module.cmplxdat.my_array == np.array([1.+2.j, -3.+4.j]))
assert np.all(self.module.cmplxdat.my_real_array == np.array([1., 2., 3.]))
assert np.all(self.module.cmplxdat.ref_index_one == np.array([13.0 + 21.0j]))
assert np.all(self.module.cmplxdat.ref_index_two == np.array([-30.0 + 43.0j]))
def test_crackedlines(self):
mod = crackfortran(self.sources)
assert mod[0]['vars']['x']['='] == '1.5'
assert mod[0]['vars']['y']['='] == '2.0'
assert mod[0]['vars']['pi']['='] == '3.1415926535897932384626433832795028841971693993751058209749445923078164062d0'
assert mod[0]['vars']['my_real_array']['='] == '(/1.0d0, 2.0d0, 3.0d0/)'
assert mod[0]['vars']['ref_index_one']['='] == '(13.0d0, 21.0d0)'
assert mod[0]['vars']['ref_index_two']['='] == '(-30.0d0, 43.0d0)'
assert mod[0]['vars']['my_array']['='] == '(/(1.0d0, 2.0d0), (-3.0d0, 4.0d0)/)'
assert mod[0]['vars']['z']['='] == '(/3.5, 7.0/)'
class TestDataF77(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "data_common.f")]
def test_data_stmts(self):
assert self.module.mycom.mydata == 0
def test_crackedlines(self):
mod = crackfortran(str(self.sources[0]))
print(mod[0]['vars'])
assert mod[0]['vars']['mydata']['='] == '0'
class TestDataMultiplierF77(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "data_multiplier.f")]
def test_data_stmts(self):
assert self.module.mycom.ivar1 == 3
assert self.module.mycom.ivar2 == 3
assert self.module.mycom.ivar3 == 2
assert self.module.mycom.ivar4 == 2
assert self.module.mycom.evar5 == 0
class TestDataWithCommentsF77(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "data_with_comments.f")]
def test_data_stmts(self):
assert len(self.module.mycom.mytab) == 3
assert self.module.mycom.mytab[0] == 0
assert self.module.mycom.mytab[1] == 4
assert self.module.mycom.mytab[2] == 0
.\numpy\numpy\f2py\tests\test_docs.py
import pytest
import numpy as np
from numpy.testing import assert_array_equal, assert_equal
from . import util
from pathlib import Path
def get_docdir():
parents = Path(__file__).resolve().parents
try:
nproot = parents[8]
except IndexError:
docdir = None
else:
docdir = nproot / "doc" / "source" / "f2py" / "code"
if docdir and docdir.is_dir():
return docdir
return parents[3] / "doc" / "source" / "f2py" / "code"
pytestmark = pytest.mark.skipif(
not get_docdir().is_dir(),
reason=f"Could not find f2py documentation sources"
f"({get_docdir()} does not exist)",
)
def _path(*args):
return get_docdir().joinpath(*args)
@pytest.mark.slow
class TestDocAdvanced(util.F2PyTest):
sources = [_path('asterisk1.f90'), _path('asterisk2.f90'),
_path('ftype.f')]
def test_asterisk1(self):
foo = getattr(self.module, 'foo1')
assert_equal(foo(), b'123456789A12')
def test_asterisk2(self):
foo = getattr(self.module, 'foo2')
assert_equal(foo(2), b'12')
assert_equal(foo(12), b'123456789A12')
assert_equal(foo(20), b'123456789A123456789B')
def test_ftype(self):
ftype = self.module
ftype.foo()
assert_equal(ftype.data.a, 0)
ftype.data.a = 3
ftype.data.x = [1, 2, 3]
assert_equal(ftype.data.a, 3)
assert_array_equal(ftype.data.x,
np.array([1, 2, 3], dtype=np.float32))
ftype.data.x[1] = 45
assert_array_equal(ftype.data.x,
np.array([1, 45, 3], dtype=np.float32))
.\numpy\numpy\f2py\tests\test_f2cmap.py
from . import util
import numpy as np
class TestF2Cmap(util.F2PyTest):
sources = [
util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90"),
util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap")
]
def test_gh15095(self):
inp = np.ones(3)
out = self.module.func1(inp)
exp_out = 3
assert out == exp_out
.\numpy\numpy\f2py\tests\test_f2py2e.py
import textwrap, re, sys, subprocess, shlex
from pathlib import Path
from collections import namedtuple
import platform
import pytest
from . import util
from numpy.f2py.f2py2e import main as f2pycli
PPaths = namedtuple("PPaths", "finp, f90inp, pyf, wrap77, wrap90, cmodf")
def get_io_paths(fname_inp, mname="untitled"):
"""获取输入文件的路径和生成文件的可能路径
这个函数用于生成测试时需要的输入和输出文件路径。
..note::
由于这并不实际运行 f2py,因此生成的文件并不一定存在,模块名通常也是错误的
Parameters
----------
fname_inp : str
输入文件名
mname : str, optional
模块名,默认为 untitled
Returns
-------
genp : NamedTuple PPaths
可能的生成文件路径集合,不一定全部存在
"""
bpath = Path(fname_inp)
return PPaths(
finp=bpath.with_suffix(".f"),
f90inp=bpath.with_suffix(".f90"),
pyf=bpath.with_suffix(".pyf"),
wrap77=bpath.with_name(f"{mname}-f2pywrappers.f"),
wrap90=bpath.with_name(f"{mname}-f2pywrappers2.f90"),
cmodf=bpath.with_name(f"{mname}module.c"),
)
@pytest.fixture(scope="session")
def hello_world_f90(tmpdir_factory):
"""生成一个用于测试的单个 f90 文件"""
fdat = util.getpath("tests", "src", "cli", "hiworld.f90").read_text()
fn = tmpdir_factory.getbasetemp() / "hello.f90"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def gh23598_warn(tmpdir_factory):
"""用于测试 gh23598 中警告的 f90 文件"""
fdat = util.getpath("tests", "src", "crackfortran", "gh23598Warn.f90").read_text()
fn = tmpdir_factory.getbasetemp() / "gh23598Warn.f90"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def gh22819_cli(tmpdir_factory):
"""用于测试 ghff819 中不允许的 CLI 参数的 f90 文件"""
fdat = util.getpath("tests", "src", "cli", "gh_22819.pyf").read_text()
fn = tmpdir_factory.getbasetemp() / "gh_22819.pyf"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def hello_world_f77(tmpdir_factory):
"""生成一个用于测试的单个 f77 文件"""
fdat = util.getpath("tests", "src", "cli", "hi77.f").read_text()
fn = tmpdir_factory.getbasetemp() / "hello.f"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def retreal_f77(tmpdir_factory):
"""生成一个用于测试的单个 f77 文件"""
fdat = util.getpath("tests", "src", "return_real", "foo77.f").read_text()
fn = tmpdir_factory.getbasetemp() / "foo.f"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def f2cmap_f90(tmpdir_factory):
fdat = util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90").read_text()
f2cmap = util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap").read_text()
fn = tmpdir_factory.getbasetemp() / "f2cmap.f90"
fmap = tmpdir_factory.getbasetemp() / "mapfile"
fn.write_text(fdat, encoding="ascii")
fmap.write_text(f2cmap, encoding="ascii")
return fn
def test_gh22819_cli(capfd, gh22819_cli, monkeypatch):
"""Check that module names are handled correctly
gh-22819
Essentially, the -m name cannot be used to import the module, so the module
named in the .pyf needs to be used instead
CLI :: -m and a .pyf file
"""
ipath = Path(gh22819_cli)
monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath}".split())
with util.switchdir(ipath.parent):
f2pycli()
gen_paths = [item.name for item in ipath.parent.rglob("*") if item.is_file()]
assert "blahmodule.c" not in gen_paths
assert "blah-f2pywrappers.f" not in gen_paths
assert "test_22819-f2pywrappers.f" in gen_paths
assert "test_22819module.c" in gen_paths
assert "Ignoring blah"
def test_gh22819_many_pyf(capfd, gh22819_cli, monkeypatch):
"""Only one .pyf file allowed
gh-22819
CLI :: .pyf files
"""
ipath = Path(gh22819_cli)
monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath} hello.pyf".split())
with util.switchdir(ipath.parent):
with pytest.raises(ValueError, match="Only one .pyf file per call"):
f2pycli()
def test_gh23598_warn(capfd, gh23598_warn, monkeypatch):
foutl = get_io_paths(gh23598_warn, mname="test")
ipath = foutl.f90inp
monkeypatch.setattr(
sys, "argv",
f'f2py {ipath} -m test'.split())
with util.switchdir(ipath.parent):
f2pycli()
wrapper = foutl.wrap90.read_text()
assert "intproductf2pywrap, intpr" not in wrapper
def test_gen_pyf(capfd, hello_world_f90, monkeypatch):
"""Ensures that a signature file is generated via the CLI
CLI :: -h
"""
ipath = Path(hello_world_f90)
opath = Path(hello_world_f90).stem + ".pyf"
monkeypatch.setattr(sys, "argv", f'f2py -h {opath} {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
assert Path(f'{opath}').exists()
def test_gen_pyf_stdout(capfd, hello_world_f90, monkeypatch):
"""Ensures that a signature file can be dumped to stdout
CLI :: -h
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -h stdout {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
assert "function hi() ! in " in out
def test_gen_pyf_no_overwrite(capfd, hello_world_f90, monkeypatch):
"""Ensures that the CLI refuses to overwrite signature files
CLI :: -h without --overwrite-signature
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -h faker.pyf {ipath}'.split())
with util.switchdir(ipath.parent):
Path("faker.pyf").write_text("Fake news", encoding="ascii")
with pytest.raises(SystemExit):
f2pycli()
_, err = capfd.readouterr()
assert "Use --overwrite-signature to overwrite" in err
@pytest.mark.skipif((platform.system() != 'Linux') or (sys.version_info <= (3, 12)),
reason='Compiler and 3.12 required')
def test_untitled_cli(capfd, hello_world_f90, monkeypatch):
"""Check that modules are named correctly
CLI :: defaults
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f"f2py --backend meson -c {ipath}".split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "untitledmodule.c" in out
@pytest.mark.skipif((platform.system() != 'Linux') or (sys.version_info <= (3, 12)), reason='Compiler and 3.12 required')
def test_no_py312_distutils_fcompiler(capfd, hello_world_f90, monkeypatch):
"""Check that no distutils imports are performed on 3.12
CLI :: --fcompiler --help-link --backend distutils
"""
MNAME = "hi"
foutl = get_io_paths(hello_world_f90, mname=MNAME)
ipath = foutl.f90inp
monkeypatch.setattr(
sys, "argv", f"f2py {ipath} -c --fcompiler=gfortran -m {MNAME}".split()
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "--fcompiler cannot be used with meson" in out
monkeypatch.setattr(
sys, "argv", f"f2py --help-link".split()
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Use --dep for meson builds" in out
MNAME = "hi2"
monkeypatch.setattr(
sys, "argv", f"f2py {ipath} -c -m {MNAME} --backend distutils".split()
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Cannot use distutils backend with Python>=3.12" in out
@pytest.mark.xfail
def test_f2py_skip(capfd, retreal_f77, monkeypatch):
"""Tests that functions can be skipped
CLI :: skip:
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
remaining = "td s0"
monkeypatch.setattr(
sys, "argv",
f'f2py {ipath} -m test skip: {toskip}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not found the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in remaining.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_f2py_only(capfd, retreal_f77, monkeypatch):
"""Test that functions can be kept by only:
CLI :: only:
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
tokeep = "td s0"
monkeypatch.setattr(
sys, "argv",
f'f2py {ipath} -m test only: {tokeep}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in tokeep.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_file_processing_switch(capfd, hello_world_f90, retreal_f77,
monkeypatch):
"""Tests that it is possible to return to file processing mode
CLI :: :
BUG: numpy-gh #20520
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
ipath2 = Path(hello_world_f90)
tokeep = "td s0 hi"
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -m {mname} only: {tokeep} : {ipath2}'.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in tokeep.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_mod_gen_f77(capfd, hello_world_f90, monkeypatch):
"""Checks the generation of files based on a module name
CLI :: -m
"""
MNAME = "hi"
foutl = get_io_paths(hello_world_f90, mname=MNAME)
ipath = foutl.f90inp
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME}'.split())
with util.switchdir(ipath.parent):
f2pycli()
assert Path.exists(foutl.cmodf)
assert Path.exists(foutl.wrap77)
def test_mod_gen_gh25263(capfd, hello_world_f77, monkeypatch):
"""Check that pyf files are correctly generated with module structure
CLI :: -m <name> -h pyf_file
BUG: numpy-gh #20520
"""
MNAME = "hi"
foutl = get_io_paths(hello_world_f77, mname=MNAME)
ipath = foutl.finp
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME} -h hi.pyf'.split())
with util.switchdir(ipath.parent):
f2pycli()
with Path('hi.pyf').open() as hipyf:
pyfdat = hipyf.read()
assert "python module hi" in pyfdat
def test_lower_cmod(capfd, hello_world_f77, monkeypatch):
"""Lowers cases by flag or when -h is present
CLI :: --[no-]lower
"""
foutl = get_io_paths(hello_world_f77, mname="test")
ipath = foutl.finp
capshi = re.compile(r"HI\(\)")
capslo = re.compile(r"hi\(\)")
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m test --lower'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is not None
assert capshi.search(out) is None
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m test --no-lower'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is None
assert capshi.search(out) is not None
def test_lower_sig(capfd, hello_world_f77, monkeypatch):
"""Lowers cases in signature files by flag or when -h is present
CLI :: --[no-]lower -h
"""
foutl = get_io_paths(hello_world_f77, mname="test")
ipath = foutl.finp
capshi = re.compile(r"Block: HI")
capslo = re.compile(r"Block: hi")
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature'.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is not None
assert capshi.search(out) is None
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature --no-lower'
.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is None
assert capshi.search(out) is not None
def test_build_dir(capfd, hello_world_f90, monkeypatch):
"""Ensures that the build directory can be specified
CLI :: --build-dir
"""
ipath = Path(hello_world_f90)
mname = "blah"
odir = "tttmp"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --build-dir {odir}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert f"Wrote C/API module \"{mname}\"" in out
def test_overwrite(capfd, hello_world_f90, monkeypatch):
"""Ensures that the build directory can be specified
CLI :: --overwrite-signature
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(
sys, "argv",
f'f2py -h faker.pyf {ipath} --overwrite-signature'.split())
with util.switchdir(ipath.parent):
Path("faker.pyf").write_text("Fake news", encoding="ascii")
f2pycli()
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
def test_latexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --latex-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --latex-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" in out
with Path(f"{mname}module.tex").open() as otex:
assert "\\documentclass" in otex.read()
def test_nolatexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --no-latex-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-latex-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" not in out
def test_shortlatex(capfd, hello_world_f90, monkeypatch):
"""Ensures that truncated documentation is written out
TODO: Test to ensure this has no effect without --latex-doc
CLI :: --latex-doc --short-latex
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py -m {mname} {ipath} --latex-doc --short-latex'.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" in out
with Path(f"./{mname}module.tex").open() as otex:
assert "\\documentclass" not in otex.read()
def test_restdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that RsT documentation is written out
CLI :: --rest-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --rest-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "ReST Documentation is saved to file" in out
with Path(f"./{mname}module.rest").open() as orst:
assert r".. -*- rest -*-" in orst.read()
def test_norestexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --no-rest-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-rest-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "ReST Documentation is saved to file" not in out
def test_debugcapi(capfd, hello_world_f90, monkeypatch):
"""Ensures that debugging wrappers are written
CLI :: --debug-capi
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --debug-capi'.split())
with util.switchdir(ipath.parent):
f2pycli()
with Path(f"./{mname}module.c").open() as ocmod:
assert r"#define DEBUGCFUNCS" in ocmod.read()
@pytest.mark.skip(reason="Consistently fails on CI; noisy so skip not xfail.")
def test_debugcapi_bld(hello_world_f90, monkeypatch):
"""Ensures that debugging wrappers work
CLI :: --debug-capi -c
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} -c --debug-capi'.split())
with util.switchdir(ipath.parent):
f2pycli()
cmd_run = shlex.split("python3 -c \"import blah; blah.hi()\"")
rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
eout = ' Hello World\n'
eerr = textwrap.dedent("""\
debug-capi:Python C/API function blah.hi()
debug-capi:float hi=:output,hidden,scalar
debug-capi:hi=0
debug-capi:Fortran subroutine `f2pywraphi(&hi)'
# 设置一个 debug-capi 标签的日志条目,表明当前 hi 值为 0
debug-capi:hi=0
# 标记 debug-capi,指示正在构建返回值
debug-capi:Building return value.
# 记录 Python C/API 函数 blah.hi 的成功执行
debug-capi:Python C/API function blah.hi: successful.
# 释放内存的 debug-capi 标签日志条目
debug-capi:Freeing memory.
""")
assert rout.stdout == eout
assert rout.stderr == eerr
def test_wrapfunc_def(capfd, hello_world_f90, monkeypatch):
"""Ensures that fortran subroutine wrappers for F77 are included by default
CLI :: --[no]-wrap-functions
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv", f'f2py -m {mname} {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" in out
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --wrap-functions'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" in out
def test_nowrapfunc(capfd, hello_world_f90, monkeypatch):
"""Ensures that fortran subroutine wrappers for F77 can be disabled
CLI :: --no-wrap-functions
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-wrap-functions'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" not in out
def test_inclheader(capfd, hello_world_f90, monkeypatch):
"""Add to the include directories
CLI :: -include
TODO: Document this in the help string
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py -m {mname} {ipath} -include<stdbool.h> -include<stdio.h> '.
split(),
)
with util.switchdir(ipath.parent):
f2pycli()
with Path(f"./{mname}module.c").open() as ocmod:
ocmr = ocmod.read()
assert "#include <stdbool.h>" in ocmr
assert "#include <stdio.h>" in ocmr
def test_inclpath():
"""Add to the include directories
CLI :: --include-paths
"""
pass
def test_hlink():
"""Add to the include directories
CLI :: --help-link
"""
pass
def test_f2cmap(capfd, f2cmap_f90, monkeypatch):
"""Check that Fortran-to-Python KIND specs can be passed
CLI :: --f2cmap
"""
ipath = Path(f2cmap_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --f2cmap mapfile'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Reading f2cmap from 'mapfile' ..." in out
assert "Mapping \"real(kind=real32)\" to \"float\"" in out
assert "Mapping \"real(kind=real64)\" to \"double\"" in out
assert "Mapping \"integer(kind=int64)\" to \"long_long\"" in out
assert "Successfully applied user defined f2cmap changes" in out
def test_quiet(capfd, hello_world_f90, monkeypatch):
"""Reduce verbosity
CLI :: --quiet
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --quiet'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert len(out) == 0
def test_verbose(capfd, hello_world_f90, monkeypatch):
"""Increase verbosity
CLI :: --verbose
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --verbose'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "analyzeline" in out
def test_version(capfd, monkeypatch):
"""Ensure version
CLI :: -v
"""
monkeypatch.setattr(sys, "argv", 'f2py -v'.split())
with pytest.raises(SystemExit):
f2pycli()
out, _ = capfd.readouterr()
import numpy as np
assert np.__version__ == out.strip()
@pytest.mark.skip(reason="Consistently fails on CI; noisy so skip not xfail.")
def test_npdistop(hello_world_f90, monkeypatch):
"""
CLI :: -c
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c'.split())
with util.switchdir(ipath.parent):
f2pycli()
cmd_run = shlex.split("python -c \"import blah; blah.hi()\"")
rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
eout = ' Hello World\n'
assert rout.stdout == eout
def test_npd_fcompiler():
"""
CLI :: -c --fcompiler
"""
pass
def test_npd_compiler():
"""
CLI :: -c --compiler
"""
pass
def test_npd_help_fcompiler():
"""
CLI :: -c --help-fcompiler
"""
pass
def test_npd_f77exec():
"""
CLI :: -c --f77exec
"""
pass
def test_npd_f90exec():
"""
CLI :: -c --f90exec
"""
pass
def test_npd_f77flags():
"""
CLI :: -c --f77flags
"""
pass
def test_npd_f90flags():
"""
CLI :: -c --f90flags
"""
pass
def test_npd_opt():
"""
CLI :: -c --opt
"""
pass
def test_npd_arch():
"""
CLI :: -c --arch
"""
pass
def test_npd_noopt():
"""
CLI :: -c --noopt
"""
pass
def test_npd_noarch():
"""
CLI :: -c --noarch
"""
pass
def test_npd_debug():
"""
CLI :: -c --debug
"""
pass
def test_npd_link_auto():
"""
CLI :: -c --link-<resource>
"""
pass
def test_npd_lib():
"""
CLI :: -c -L/path/to/lib/ -l<libname>
"""
pass
def test_npd_define():
"""
CLI :: -D<define>
"""
pass
def test_npd_undefine():
"""
CLI :: -U<name>
"""
pass
def test_npd_incl():
"""
CLI :: -I/path/to/include/
"""
pass
def test_npd_linker():
"""
CLI :: <filename>.o <filename>.so <filename>.a
"""
pass