summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorPearu Peterson <pearu.peterson@gmail.com>2021-07-01 17:54:45 +0300
committerRohit Goswami <rog32@hi.is>2022-06-05 15:19:12 +0000
commitd4e11c7a2eb64861275facb076d47ccd135fa28c (patch)
tree307e937f15807094ae81b8a78d791bab4526c851 /numpy
parente5fcb9d52c1b9d3c9caf067849ad1bba128c7e17 (diff)
downloadnumpy-d4e11c7a2eb64861275facb076d47ccd135fa28c.tar.gz
ENH: Support character string arrays
TST: added test for issue #18684 ENH: f2py opens files with correct encoding, fixes #635 TST: added test for issue #6308 TST: added test for issue #4519 TST: added test for issue #3425 ENH: Implement user-defined hooks support for post-processing f2py data structure. Implement character BC hook. ENH: Add support for detecting utf-16 and utf-32 encodings.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/f2py/auxfuncs.py71
-rw-r--r--numpy/f2py/capi_maps.py61
-rw-r--r--numpy/f2py/cb_rules.py25
-rw-r--r--numpy/f2py/cfuncs.py133
-rw-r--r--numpy/f2py/common_rules.py8
-rwxr-xr-xnumpy/f2py/crackfortran.py243
-rw-r--r--numpy/f2py/f90mod_rules.py20
-rw-r--r--numpy/f2py/func2subr.py27
-rwxr-xr-xnumpy/f2py/rules.py183
-rw-r--r--numpy/f2py/src/fortranobject.c461
-rw-r--r--numpy/f2py/src/fortranobject.h28
-rw-r--r--numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c7
-rw-r--r--numpy/f2py/tests/test_array_from_pyobj.py81
-rw-r--r--numpy/f2py/tests/test_callback.py22
-rw-r--r--numpy/f2py/tests/test_character.py570
-rw-r--r--numpy/f2py/tests/test_crackfortran.py31
-rw-r--r--numpy/f2py/tests/test_docs.py55
-rw-r--r--numpy/f2py/tests/test_return_character.py2
-rw-r--r--numpy/f2py/tests/util.py10
19 files changed, 1693 insertions, 345 deletions
diff --git a/numpy/f2py/auxfuncs.py b/numpy/f2py/auxfuncs.py
index c8f2067c9..98c9eff78 100644
--- a/numpy/f2py/auxfuncs.py
+++ b/numpy/f2py/auxfuncs.py
@@ -28,10 +28,12 @@ __all__ = [
'getfortranname', 'getpymethoddef', 'getrestdoc', 'getusercode',
'getusercode1', 'hasbody', 'hascallstatement', 'hascommon',
'hasexternals', 'hasinitvalue', 'hasnote', 'hasresultnote',
- 'isallocatable', 'isarray', 'isarrayofstrings', 'iscomplex',
+ 'isallocatable', 'isarray', 'isarrayofstrings',
+ 'ischaracter', 'ischaracterarray', 'ischaracter_or_characterarray',
+ 'iscomplex',
'iscomplexarray', 'iscomplexfunction', 'iscomplexfunction_warn',
'isdouble', 'isdummyroutine', 'isexternal', 'isfunction',
- 'isfunction_wrap', 'isint1array', 'isinteger', 'isintent_aux',
+ 'isfunction_wrap', 'isint1', 'isint1array', 'isinteger', 'isintent_aux',
'isintent_c', 'isintent_callback', 'isintent_copy', 'isintent_dict',
'isintent_hide', 'isintent_in', 'isintent_inout', 'isintent_inplace',
'isintent_nothide', 'isintent_out', 'isintent_overwrite', 'islogical',
@@ -39,7 +41,8 @@ __all__ = [
'islong_doublefunction', 'islong_long', 'islong_longfunction',
'ismodule', 'ismoduleroutine', 'isoptional', 'isprivate', 'isrequired',
'isroutine', 'isscalar', 'issigned_long_longarray', 'isstring',
- 'isstringarray', 'isstringfunction', 'issubroutine',
+ 'isstringarray', 'isstring_or_stringarray', 'isstringfunction',
+ 'issubroutine',
'issubroutine_wrap', 'isthreadsafe', 'isunsigned', 'isunsigned_char',
'isunsigned_chararray', 'isunsigned_long_long',
'isunsigned_long_longarray', 'isunsigned_short',
@@ -68,24 +71,41 @@ def debugcapi(var):
return 'capi' in debugoptions
+def _ischaracter(var):
+ return 'typespec' in var and var['typespec'] == 'character' and \
+ not isexternal(var)
+
+
def _isstring(var):
return 'typespec' in var and var['typespec'] == 'character' and \
not isexternal(var)
-def isstring(var):
- return _isstring(var) and not isarray(var)
+def ischaracter_or_characterarray(var):
+ return _ischaracter(var) and 'charselector' not in var
def ischaracter(var):
- return isstring(var) and 'charselector' not in var
+ return ischaracter_or_characterarray(var) and not isarray(var)
+
+
+def ischaracterarray(var):
+ return ischaracter_or_characterarray(var) and isarray(var)
+
+
+def isstring_or_stringarray(var):
+ return _ischaracter(var) and 'charselector' in var
+
+
+def isstring(var):
+ return isstring_or_stringarray(var) and not isarray(var)
def isstringarray(var):
- return isarray(var) and _isstring(var)
+ return isstring_or_stringarray(var) and isarray(var)
-def isarrayofstrings(var):
+def isarrayofstrings(var): # obsolete?
# leaving out '*' for now so that `character*(*) a(m)` and `character
# a(m,*)` are treated differently. Luckily `character**` is illegal.
return isstringarray(var) and var['dimension'][-1] == '(*)'
@@ -126,6 +146,11 @@ def get_kind(var):
pass
+def isint1(var):
+ return var.get('typespec') == 'integer' \
+ and get_kind(var) == '1' and not isarray(var)
+
+
def islong_long(var):
if not isscalar(var):
return 0
@@ -426,6 +451,7 @@ def isintent_hide(var):
('out' in var['intent'] and 'in' not in var['intent'] and
(not l_or(isintent_inout, isintent_inplace)(var)))))
+
def isintent_nothide(var):
return not isintent_hide(var)
@@ -469,6 +495,7 @@ def isintent_aligned8(var):
def isintent_aligned16(var):
return 'aligned16' in var.get('intent', [])
+
isintent_dict = {isintent_in: 'INTENT_IN', isintent_inout: 'INTENT_INOUT',
isintent_out: 'INTENT_OUT', isintent_hide: 'INTENT_HIDE',
isintent_cache: 'INTENT_CACHE',
@@ -566,19 +593,19 @@ class throw_error:
def l_and(*f):
- l, l2 = 'lambda v', []
+ l1, l2 = 'lambda v', []
for i in range(len(f)):
- l = '%s,f%d=f[%d]' % (l, i, i)
+ l1 = '%s,f%d=f[%d]' % (l1, i, i)
l2.append('f%d(v)' % (i))
- return eval('%s:%s' % (l, ' and '.join(l2)))
+ return eval('%s:%s' % (l1, ' and '.join(l2)))
def l_or(*f):
- l, l2 = 'lambda v', []
+ l1, l2 = 'lambda v', []
for i in range(len(f)):
- l = '%s,f%d=f[%d]' % (l, i, i)
+ l1 = '%s,f%d=f[%d]' % (l1, i, i)
l2.append('f%d(v)' % (i))
- return eval('%s:%s' % (l, ' or '.join(l2)))
+ return eval('%s:%s' % (l1, ' or '.join(l2)))
def l_not(f):
@@ -666,7 +693,9 @@ def getcallprotoargument(rout, cb_map={}):
pass
else:
ctype = ctype + '*'
- if isstring(var) or isarrayofstrings(var):
+ if ((isstring(var)
+ or isarrayofstrings(var) # obsolete?
+ or isstringarray(var))):
arg_types2.append('size_t')
arg_types.append(ctype)
@@ -731,14 +760,14 @@ def getrestdoc(rout):
def gentitle(name):
- l = (80 - len(name) - 6) // 2
- return '/*%s %s %s*/' % (l * '*', name, l * '*')
+ ln = (80 - len(name) - 6) // 2
+ return '/*%s %s %s*/' % (ln * '*', name, ln * '*')
-def flatlist(l):
- if isinstance(l, list):
- return reduce(lambda x, y, f=flatlist: x + f(y), l, [])
- return [l]
+def flatlist(lst):
+ if isinstance(lst, list):
+ return reduce(lambda x, y, f=flatlist: x + f(y), lst, [])
+ return [lst]
def stripcomma(s):
diff --git a/numpy/f2py/capi_maps.py b/numpy/f2py/capi_maps.py
index 1f43207db..e5dc2331a 100644
--- a/numpy/f2py/capi_maps.py
+++ b/numpy/f2py/capi_maps.py
@@ -56,6 +56,7 @@ c2py_map = {'double': 'float',
'complex_double': 'complex',
'complex_long_double': 'complex', # forced casting
'string': 'string',
+ 'character': 'bytes',
}
c2capi_map = {'double': 'NPY_DOUBLE',
'float': 'NPY_FLOAT',
@@ -72,7 +73,8 @@ c2capi_map = {'double': 'NPY_DOUBLE',
'complex_float': 'NPY_CFLOAT',
'complex_double': 'NPY_CDOUBLE',
'complex_long_double': 'NPY_CDOUBLE', # forced casting
- 'string': 'NPY_STRING'}
+ 'string': 'NPY_STRING',
+ 'character': 'NPY_CHAR'}
# These new maps aren't used anywhere yet, but should be by default
# unless building numeric or numarray extensions.
@@ -94,8 +96,8 @@ if using_newcore:
'complex_float': 'NPY_CFLOAT',
'complex_double': 'NPY_CDOUBLE',
'complex_long_double': 'NPY_CDOUBLE',
- 'string':'NPY_STRING'
- }
+ 'string': 'NPY_STRING',
+ 'character': 'NPY_STRING'}
c2pycode_map = {'double': 'd',
'float': 'f',
@@ -112,7 +114,8 @@ c2pycode_map = {'double': 'd',
'complex_float': 'F',
'complex_double': 'D',
'complex_long_double': 'D', # forced casting
- 'string': 'c'
+ 'string': 'c',
+ 'character': 'c'
}
if using_newcore:
@@ -133,8 +136,11 @@ if using_newcore:
'complex_float': 'F',
'complex_double': 'D',
'complex_long_double': 'G',
- 'string': 'S'}
+ 'string': 'S',
+ 'character': 'c'}
+# https://docs.python.org/3/c-api/arg.html#building-values
+# c2buildvalue_map is NumPy agnostic, so no need to bother with using_newcore
c2buildvalue_map = {'double': 'd',
'float': 'f',
'char': 'b',
@@ -146,7 +152,8 @@ c2buildvalue_map = {'double': 'd',
'complex_float': 'N',
'complex_double': 'N',
'complex_long_double': 'N',
- 'string': 'y'}
+ 'string': 'y',
+ 'character': 'c'}
f2cmap_all = {'real': {'': 'float', '4': 'float', '8': 'double',
'12': 'long_double', '16': 'long_double'},
@@ -165,7 +172,6 @@ f2cmap_all = {'real': {'': 'float', '4': 'float', '8': 'double',
'double complex': {'': 'complex_double'},
'double precision': {'': 'double'},
'byte': {'': 'char'},
- 'character': {'': 'string'}
}
f2cmap_default = copy.deepcopy(f2cmap_all)
@@ -230,7 +236,8 @@ cformat_map = {'double': '%g',
'complex_float': '(%g,%g)',
'complex_double': '(%g,%g)',
'complex_long_double': '(%Lg,%Lg)',
- 'string': '%s',
+ 'string': '\\"%s\\"',
+ 'character': "'%c'",
}
# Auxiliary functions
@@ -252,6 +259,10 @@ def getctype(var):
errmess('getctype: function %s has no return value?!\n' % a)
elif issubroutine(var):
return ctype
+ elif ischaracter_or_characterarray(var):
+ return 'character'
+ elif isstring_or_stringarray(var):
+ return 'string'
elif 'typespec' in var and var['typespec'].lower() in f2cmap_all:
typespec = var['typespec'].lower()
f2cmap = f2cmap_all[typespec]
@@ -283,6 +294,21 @@ def getctype(var):
return ctype
+def f2cexpr(expr):
+ """Rewrite Fortran expression as f2py supported C expression.
+
+ Due to the lack of a proper expression parser in f2py, this
+ function uses a heuristic approach that assumes that Fortran
+ arithmetic expressions are valid C arithmetic expressions when
+ mapping Fortran function calls to the corresponding C function/CPP
+ macros calls.
+
+ """
+ # TODO: support Fortran `len` function with optional kind parameter
+ expr = re.sub(r'\blen\b', 'f2py_slen', expr)
+ return expr
+
+
def getstrlength(var):
if isstringfunction(var):
if 'result' in var:
@@ -302,7 +328,7 @@ def getstrlength(var):
if '*' in a:
len = a['*']
elif 'len' in a:
- len = a['len']
+ len = f2cexpr(a['len'])
if re.match(r'\(\s*(\*|:)\s*\)', len) or re.match(r'(\*|:)', len):
if isintent_hide(var):
errmess('getstrlength:intent(hide): expected a string with defined length but got: %s\n' % (
@@ -499,6 +525,20 @@ def getinit(a, var):
return init, showinit
+def get_elsize(var):
+ if isstring(var) or isstringarray(var):
+ elsize = getstrlength(var)
+ # override with user-specified length when available:
+ elsize = var['charselector'].get('f2py_len', elsize)
+ return elsize
+ if ischaracter(var) or ischaracterarray(var):
+ return '1'
+ # for numerical types, PyArray_New* functions ignore specified
+ # elsize, so we just return 1 and let elsize be determined at
+ # runtime, see fortranobject.c
+ return '1'
+
+
def sign2map(a, var):
"""
varname,ctype,atype
@@ -553,6 +593,7 @@ def sign2map(a, var):
dim = copy.copy(var['dimension'])
if ret['ctype'] in c2capi_map:
ret['atype'] = c2capi_map[ret['ctype']]
+ ret['elsize'] = get_elsize(var)
# Debug info
if debugcapi(var):
il = [isintent_in, 'input', isintent_out, 'output',
@@ -720,6 +761,7 @@ def cb_sign2map(a, var, index=None):
ret['ctype'] = getctype(var)
if ret['ctype'] in c2capi_map:
ret['atype'] = c2capi_map[ret['ctype']]
+ ret['elsize'] = get_elsize(var)
if ret['ctype'] in cformat_map:
ret['showvalueformat'] = '%s' % (cformat_map[ret['ctype']])
if isarray(var):
@@ -819,6 +861,7 @@ def common_sign2map(a, var): # obsolute
ret['ctype'] = 'char'
if ret['ctype'] in c2capi_map:
ret['atype'] = c2capi_map[ret['ctype']]
+ ret['elsize'] = get_elsize(var)
if ret['ctype'] in cformat_map:
ret['showvalueformat'] = '%s' % (cformat_map[ret['ctype']])
if isarray(var):
diff --git a/numpy/f2py/cb_rules.py b/numpy/f2py/cb_rules.py
index c2d7faaad..761831e00 100644
--- a/numpy/f2py/cb_rules.py
+++ b/numpy/f2py/cb_rules.py
@@ -424,8 +424,11 @@ cb_arg_rules = [
{debugcapi: 'CFUNCSMESS'}, 'string.h'],
'_check': l_and(isstring, isintent_out)
}, {
- 'pyobjfrom': [{debugcapi: ' fprintf(stderr,"debug-capi:cb:#varname#=\\"#showvalueformat#\\":%d:\\n",#varname_i#,#varname_i#_cb_len);'},
- {isintent_in: """\
+ 'pyobjfrom': [
+ {debugcapi:
+ (' fprintf(stderr,"debug-capi:cb:#varname#=#showvalueformat#:'
+ '%d:\\n",#varname_i#,#varname_i#_cb_len);')},
+ {isintent_in: """\
if (cb->nofargs>capi_i)
if (CAPI_ARGLIST_SETITEM(capi_i++,pyobj_from_#ctype#1size(#varname_i#,#varname_i#_cb_len)))
goto capi_fail;"""},
@@ -451,15 +454,21 @@ cb_arg_rules = [
'pyobjfrom': [{debugcapi: ' fprintf(stderr,"debug-capi:cb:#varname#\\n");'},
{isintent_c: """\
if (cb->nofargs>capi_i) {
- int itemsize_ = #atype# == NPY_STRING ? 1 : 0;
- /*XXX: Hmm, what will destroy this array??? */
- PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,#rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,itemsize_,NPY_ARRAY_CARRAY,NULL);
+ /* tmp_arr will be inserted to capi_arglist_list that will be
+ destroyed when leaving callback function wrapper together
+ with tmp_arr. */
+ PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,
+ #rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,#elsize#,
+ NPY_ARRAY_CARRAY,NULL);
""",
l_not(isintent_c): """\
if (cb->nofargs>capi_i) {
- int itemsize_ = #atype# == NPY_STRING ? 1 : 0;
- /*XXX: Hmm, what will destroy this array??? */
- PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,#rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,itemsize_,NPY_ARRAY_FARRAY,NULL);
+ /* tmp_arr will be inserted to capi_arglist_list that will be
+ destroyed when leaving callback function wrapper together
+ with tmp_arr. */
+ PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,
+ #rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,#elsize#,
+ NPY_ARRAY_FARRAY,NULL);
""",
},
"""
diff --git a/numpy/f2py/cfuncs.py b/numpy/f2py/cfuncs.py
index f69933543..d644ede8c 100644
--- a/numpy/f2py/cfuncs.py
+++ b/numpy/f2py/cfuncs.py
@@ -88,6 +88,7 @@ typedefs[
typedefs['complex_float'] = 'typedef struct {float r,i;} complex_float;'
typedefs['complex_double'] = 'typedef struct {double r,i;} complex_double;'
typedefs['string'] = """typedef char * string;"""
+typedefs['character'] = """typedef char character;"""
############### CPP macros ####################
@@ -242,47 +243,16 @@ cppmacros['MINMAX'] = """\
#define MIN(a,b) ((a < b) ? (a) : (b))
#endif
"""
-needs['len..'] = ['f2py_size']
cppmacros['len..'] = """\
-#define rank(var) var ## _Rank
-#define shape(var,dim) var ## _Dims[dim]
-#define old_rank(var) (PyArray_NDIM((PyArrayObject *)(capi_ ## var ## _tmp)))
-#define old_shape(var,dim) PyArray_DIM(((PyArrayObject *)(capi_ ## var ## _tmp)),dim)
-#define fshape(var,dim) shape(var,rank(var)-dim-1)
-#define len(var) shape(var,0)
-#define flen(var) fshape(var,0)
-#define old_size(var) PyArray_SIZE((PyArrayObject *)(capi_ ## var ## _tmp))
-/* #define index(i) capi_i ## i */
-#define slen(var) capi_ ## var ## _len
-#define size(var, ...) f2py_size((PyArrayObject *)(capi_ ## var ## _tmp), ## __VA_ARGS__, -1)
+/* See fortranobject.h for definitions. The macros here are provided for BC. */
+#define rank f2py_rank
+#define shape f2py_shape
+#define fshape f2py_shape
+#define len f2py_len
+#define flen f2py_flen
+#define slen f2py_slen
+#define size f2py_size
"""
-needs['f2py_size'] = ['stdarg.h']
-cfuncs['f2py_size'] = """\
-static int f2py_size(PyArrayObject* var, ...)
-{
- npy_int sz = 0;
- npy_int dim;
- npy_int rank;
- va_list argp;
- va_start(argp, var);
- dim = va_arg(argp, npy_int);
- if (dim==-1)
- {
- sz = PyArray_SIZE(var);
- }
- else
- {
- rank = PyArray_NDIM(var);
- if (dim>=1 && dim<=rank)
- sz = PyArray_DIM(var, dim-1);
- else
- fprintf(stderr, \"f2py_size: 2nd argument value=%d fails to satisfy 1<=value<=%d. Result will be 0.\\n\", dim, rank);
- }
- va_end(argp);
- return sz;
-}
-"""
-
cppmacros[
'pyobj_from_char1'] = '#define pyobj_from_char1(v) (PyLong_FromLong(v))'
cppmacros[
@@ -785,6 +755,66 @@ capi_fail:
}
"""
+cfuncs['character_from_pyobj'] = """\
+static int
+character_from_pyobj(character* v, PyObject *obj, const char *errmess) {
+ if (PyBytes_Check(obj)) {
+ /* empty bytes has trailing null, so dereferencing is always safe */
+ *v = PyBytes_AS_STRING(obj)[0];
+ return 1;
+ } else if (PyUnicode_Check(obj)) {
+ PyObject* tmp = PyUnicode_AsASCIIString(obj);
+ if (tmp != NULL) {
+ *v = PyBytes_AS_STRING(tmp)[0];
+ Py_DECREF(tmp);
+ return 1;
+ }
+ } else if (PyArray_Check(obj)) {
+ PyArrayObject* arr = (PyArrayObject*)obj;
+ if (F2PY_ARRAY_IS_CHARACTER_COMPATIBLE(arr)) {
+ *v = PyArray_BYTES(arr)[0];
+ return 1;
+ } else if (F2PY_IS_UNICODE_ARRAY(arr)) {
+ // TODO: update when numpy will support 1-byte and
+ // 2-byte unicode dtypes
+ PyObject* tmp = PyUnicode_FromKindAndData(
+ PyUnicode_4BYTE_KIND,
+ PyArray_BYTES(arr),
+ (PyArray_NBYTES(arr)>0?1:0));
+ if (tmp != NULL) {
+ if (character_from_pyobj(v, tmp, errmess)) {
+ Py_DECREF(tmp);
+ return 1;
+ }
+ Py_DECREF(tmp);
+ }
+ }
+ } else if (PySequence_Check(obj)) {
+ PyObject* tmp = PySequence_GetItem(obj,0);
+ if (tmp != NULL) {
+ if (character_from_pyobj(v, tmp, errmess)) {
+ Py_DECREF(tmp);
+ return 1;
+ }
+ Py_DECREF(tmp);
+ }
+ }
+ {
+ char mess[F2PY_MESSAGE_BUFFER_SIZE];
+ strcpy(mess, errmess);
+ PyObject* err = PyErr_Occurred();
+ if (err == NULL) {
+ err = PyExc_TypeError;
+ }
+ sprintf(mess + strlen(mess),
+ " -- expected str|bytes|sequence-of-str-or-bytes, got ");
+ f2py_describe(obj, mess + strlen(mess));
+ PyErr_SetString(err, mess);
+ }
+ return 0;
+}
+"""
+
needs['char_from_pyobj'] = ['int_from_pyobj']
cfuncs['char_from_pyobj'] = """\
static int
@@ -1179,6 +1209,31 @@ complex_float_from_pyobj(complex_float* v,PyObject *obj,const char *errmess)
"""
+cfuncs['try_pyarr_from_character'] = """\
+static int try_pyarr_from_character(PyObject* obj, character* v) {
+ PyArrayObject *arr = (PyArrayObject*)obj;
+ if (!obj) return -2;
+ if (PyArray_Check(obj)) {
+ if (F2PY_ARRAY_IS_CHARACTER_COMPATIBLE(arr)) {
+ *(character *)(PyArray_DATA(arr)) = *v;
+ return 1;
+ }
+ }
+ {
+ char mess[F2PY_MESSAGE_BUFFER_SIZE];
+ PyObject* err = PyErr_Occurred();
+ if (err == NULL) {
+ err = PyExc_ValueError;
+ strcpy(mess, "try_pyarr_from_character failed"
+ " -- expected bytes array-scalar|array, got ");
+ f2py_describe(obj, mess + strlen(mess));
+ }
+ PyErr_SetString(err, mess);
+ }
+ return 0;
+}
+"""
+
needs['try_pyarr_from_char'] = ['pyobj_from_char1', 'TRYPYARRAYTEMPLATE']
cfuncs[
'try_pyarr_from_char'] = 'static int try_pyarr_from_char(PyObject* obj,char* v) {\n TRYPYARRAYTEMPLATE(char,\'c\');\n}\n'
diff --git a/numpy/f2py/common_rules.py b/numpy/f2py/common_rules.py
index 937d8bc72..5a488bf5a 100644
--- a/numpy/f2py/common_rules.py
+++ b/numpy/f2py/common_rules.py
@@ -91,6 +91,7 @@ def buildhooks(m):
idims = []
for n in inames:
ct = capi_maps.getctype(vars[n])
+ elsize = capi_maps.get_elsize(vars[n])
at = capi_maps.c2capi_map[ct]
dm = capi_maps.getarrdims(n, vars[n])
if dm['dims']:
@@ -100,7 +101,8 @@ def buildhooks(m):
dms = dm['dims'].strip()
if not dms:
dms = '-1'
- cadd('\t{\"%s\",%s,{{%s}},%s},' % (n, dm['rank'], dms, at))
+ cadd('\t{\"%s\",%s,{{%s}},%s, %s},'
+ % (n, dm['rank'], dms, at, elsize))
cadd('\t{NULL}\n};')
inames1 = rmbadname(inames)
inames1_tps = ','.join(['char *' + s for s in inames1])
@@ -121,7 +123,9 @@ def buildhooks(m):
% (F_FUNC, lower_name, name.upper(), name))
cadd('}\n')
iadd('\ttmp = PyFortranObject_New(f2py_%s_def,f2py_init_%s);' % (name, name))
- iadd('\tF2PyDict_SetItemString(d, \"%s\", tmp);' % name)
+ iadd('\tif (tmp == NULL) return NULL;')
+ iadd('\tif (F2PyDict_SetItemString(d, \"%s\", tmp) == -1) return NULL;'
+ % name)
iadd('\tPy_DECREF(tmp);')
tname = name.replace('_', '\\_')
dadd('\\subsection{Common block \\texttt{%s}}\n' % (tname))
diff --git a/numpy/f2py/crackfortran.py b/numpy/f2py/crackfortran.py
index 515bdd787..cfd58dfed 100755
--- a/numpy/f2py/crackfortran.py
+++ b/numpy/f2py/crackfortran.py
@@ -84,7 +84,7 @@ Usage:
'optional','required', etc)
K = D['kindselector'] = {['*','kind']} (only if D['typespec'] =
'complex' | 'integer' | 'logical' | 'real' )
- C = D['charselector'] = {['*','len','kind']}
+ C = D['charselector'] = {['*','len','kind','f2py_len']}
(only if D['typespec']=='character')
D['='] --- initialization expression string
D['typename'] --- name of the type if D['typespec']=='type'
@@ -97,7 +97,7 @@ Usage:
D['typespec>']*K['*']
D['typespec'](kind=K['kind'])
character*C['*']
- character(len=C['len'],kind=C['kind'])
+ character(len=C['len'],kind=C['kind'], f2py_len=C['f2py_len'])
(see also fortran type declaration statement formats below)
Fortran 90 type declaration statement format (F77 is subset of F90)
@@ -146,6 +146,11 @@ import re
import os
import copy
import platform
+import codecs
+try:
+ import chardet
+except ImportError:
+ chardet = None
from . import __version__
@@ -301,12 +306,38 @@ _has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search
_free_f90_start = re.compile(r'[^c*]\s*[^\s\d\t]', re.I).match
+def openhook(filename, mode):
+ """Ensures that filename is opened with correct encoding parameter.
+
+ This function uses chardet package, when available, for
+ determining the encoding of the file to be opened. When chardet is
+ not available, the function detects only UTF encodings, otherwise,
+ ASCII encoding is used as fallback.
+ """
+ bytes = min(32, os.path.getsize(filename))
+ with open(filename, 'rb') as f:
+ raw = f.read(bytes)
+ if raw.startswith(codecs.BOM_UTF8):
+ encoding = 'UTF-8-SIG'
+ elif raw.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
+ encoding = 'UTF-32'
+ elif raw.startswith((codecs.BOM_LE, codecs.BOM_BE)):
+ encoding = 'UTF-16'
+ else:
+ if chardet is not None:
+ encoding = chardet.detect(raw)['encoding']
+ else:
+ # hint: install chardet to ensure correct encoding handling
+ encoding = 'ascii'
+ return open(filename, mode, encoding=encoding)
+
+
def is_free_format(file):
"""Check if file is in free format Fortran."""
# f90 allows both fixed and free format, assuming fixed unless
# signs of free format are detected.
result = 0
- with open(file, 'r') as f:
+ with openhook(file, 'r') as f:
line = f.readline()
n = 15 # the number of non-comment lines to scan for hints
if _has_f_header(line):
@@ -356,9 +387,16 @@ def readfortrancode(ffile, dowithline=show, istop=1):
ll, l1 = '', ''
spacedigits = [' '] + [str(_m) for _m in range(10)]
filepositiontext = ''
- fin = fileinput.FileInput(ffile)
+ fin = fileinput.FileInput(ffile, openhook=openhook)
while True:
- l = fin.readline()
+ try:
+ l = fin.readline()
+ except UnicodeDecodeError as msg:
+ raise Exception(
+ f'readfortrancode: reading {fin.filename()}#{fin.lineno()}'
+ f' failed with\n{msg}.\nIt is likely that installing chardet'
+ ' package will help f2py determine the input file encoding'
+ ' correctly.')
if not l:
break
if fin.isfirstline():
@@ -1546,7 +1584,9 @@ kindselector = re.compile(
charselector = re.compile(
r'\s*(\((?P<lenkind>.*)\)|\*\s*(?P<charlen>.*))\s*\Z', re.I)
lenkindpattern = re.compile(
- r'\s*(kind\s*=\s*(?P<kind>.*?)\s*(@,@\s*len\s*=\s*(?P<len>.*)|)|(len\s*=\s*|)(?P<len2>.*?)\s*(@,@\s*(kind\s*=\s*|)(?P<kind2>.*)|))\s*\Z', re.I)
+ r'\s*(kind\s*=\s*(?P<kind>.*?)\s*(@,@\s*len\s*=\s*(?P<len>.*)|)'
+ r'|(len\s*=\s*|)(?P<len2>.*?)\s*(@,@\s*(kind\s*=\s*|)(?P<kind2>.*)'
+ r'|(f2py_len\s*=\s*(?P<f2py_len>.*))|))\s*\Z', re.I)
lenarraypattern = re.compile(
r'\s*(@\(@\s*(?!/)\s*(?P<array>.*?)\s*@\)@\s*\*\s*(?P<len>.*?)|(\*\s*(?P<len2>.*?)|)\s*(@\(@\s*(?!/)\s*(?P<array2>.*?)\s*@\)@|))\s*(=\s*(?P<init>.*?)|(@\(@|)/\s*(?P<init2>.*?)\s*/(@\)@|)|)\s*\Z', re.I)
@@ -1788,6 +1828,9 @@ def cracktypespec(typespec, selector):
lenkind[lk] = lenkind[lk + '2']
charselect[lk] = lenkind[lk]
del lenkind[lk + '2']
+ if lenkind['f2py_len'] is not None:
+ # used to specify the length of assumed length strings
+ charselect['f2py_len'] = lenkind['f2py_len']
del charselect['lenkind']
for k in list(charselect.keys()):
if not charselect[k]:
@@ -1857,6 +1900,7 @@ def setcharselector(decl, sel, force=0):
if 'charselector' not in decl:
decl['charselector'] = sel
return decl
+
for k in list(sel.keys()):
if force or k not in decl['charselector']:
decl['charselector'][k] = sel[k]
@@ -2465,6 +2509,7 @@ def _eval_scalar(value, params):
if _is_kind_number(value):
value = value.split('_')[0]
try:
+ # TODO: use symbolic from PR #19805
value = eval(value, {}, params)
value = (repr if isinstance(value, str) else str)(value)
except (NameError, SyntaxError, TypeError):
@@ -2534,7 +2579,6 @@ def analyzevars(block):
elif n in block['args']:
outmess('analyzevars: typespec of variable %s is not defined in routine %s.\n' % (
repr(n), block['name']))
-
if 'charselector' in vars[n]:
if 'len' in vars[n]['charselector']:
l = vars[n]['charselector']['len']
@@ -2667,26 +2711,6 @@ def analyzevars(block):
dimension_exprs[d] = solver_and_deps
vars[n]['dimension'].append(d)
- if 'dimension' in vars[n]:
- if isstringarray(vars[n]):
- if 'charselector' in vars[n]:
- d = vars[n]['charselector']
- if '*' in d:
- d = d['*']
- errmess('analyzevars: character array "character*%s %s(%s)" is considered as "character %s(%s)"; "intent(c)" is forced.\n'
- % (d, n,
- ','.join(vars[n]['dimension']),
- n, ','.join(vars[n]['dimension'] + [d])))
- vars[n]['dimension'].append(d)
- del vars[n]['charselector']
- if 'intent' not in vars[n]:
- vars[n]['intent'] = []
- if 'c' not in vars[n]['intent']:
- vars[n]['intent'].append('c')
- else:
- errmess(
- "analyzevars: charselector=%r unhandled.\n" % (d))
-
if 'check' not in vars[n] and 'args' in block and n in block['args']:
# n is an argument that has no checks defined. Here we
# generate some consistency checks for n, and when n is an
@@ -3220,6 +3244,13 @@ def vars2fortran(block, vars, args, tab='', as_interface=False):
if 'attrspec' in vars[a]:
attr = [l for l in vars[a]['attrspec']
if l not in ['external']]
+ if as_interface and 'intent(in)' in attr and 'intent(out)' in attr:
+ # In Fortran, intent(in, out) are conflicting while
+ # intent(in, out) can be specified only via
+ # `!f2py intent(out) ..`.
+ # So, for the Fortran interface, we'll drop
+ # intent(out) to resolve the conflict.
+ attr.remove('intent(out)')
if attr:
vardef = '%s, %s' % (vardef, ','.join(attr))
c = ','
@@ -3255,14 +3286,23 @@ def vars2fortran(block, vars, args, tab='', as_interface=False):
######
+# We expose post_processing_hooks as global variable so that
+# user-libraries could register their own hooks to f2py.
+post_processing_hooks = []
+
+
def crackfortran(files):
- global usermodules
+ global usermodules, post_processing_hooks
outmess('Reading fortran codes...\n', 0)
readfortrancode(files, crackline)
outmess('Post-processing...\n', 0)
usermodules = []
postlist = postcrack(grouplist[0])
+ outmess('Applying post-processing hooks...\n', 0)
+ for hook in post_processing_hooks:
+ outmess(f' {hook.__name__}\n', 0)
+ postlist = traverse(postlist, hook)
outmess('Post-processing (stage 2)...\n', 0)
postlist = postcrack2(postlist)
return usermodules + postlist
@@ -3282,6 +3322,142 @@ def crack2fortran(block):
""" % (f2py_version)
return header + pyf + footer
+
+def _is_visit_pair(obj):
+ return (isinstance(obj, tuple)
+ and len(obj) == 2
+ and isinstance(obj[0], (int, str)))
+
+
+def traverse(obj, visit, parents=[], result=None, *args, **kwargs):
+ '''Traverse f2py data structure with the following visit function:
+
+ def visit(item, parents, result, *args, **kwargs):
+ """
+
+ parents is a list of key-"f2py data structure" pairs from which
+ items are taken from.
+
+ result is a f2py data structure that is filled with the
+ return value of the visit function.
+
+ item is 2-tuple (index, value) if parents[-1][1] is a list
+ item is 2-tuple (key, value) if parents[-1][1] is a dict
+
+ The return value of visit must be None, or of the same kind as
+ item, that is, if parents[-1] is a list, the return value must
+ be 2-tuple (new_index, new_value), or if parents[-1] is a
+ dict, the return value must be 2-tuple (new_key, new_value).
+
+ If new_index or new_value is None, the return value of visit
+ is ignored, that is, it will not be added to the result.
+
+ If the return value is None, the content of obj will be
+ traversed, otherwise not.
+ """
+ '''
+
+ if _is_visit_pair(obj):
+ if obj[0] == 'parent_block':
+ # avoid infinite recursion
+ return obj
+ new_result = visit(obj, parents, result, *args, **kwargs)
+ if new_result is not None:
+ assert _is_visit_pair(new_result)
+ return new_result
+ parent = obj
+ result_key, obj = obj
+ else:
+ parent = (None, obj)
+ result_key = None
+
+ if isinstance(obj, list):
+ new_result = []
+ for index, value in enumerate(obj):
+ new_index, new_item = traverse((index, value), visit,
+ parents=parents + [parent],
+ result=result, *args, **kwargs)
+ if new_index is not None:
+ new_result.append(new_item)
+ elif isinstance(obj, dict):
+ new_result = dict()
+ for key, value in obj.items():
+ new_key, new_value = traverse((key, value), visit,
+ parents=parents + [parent],
+ result=result, *args, **kwargs)
+ if new_key is not None:
+ new_result[new_key] = new_value
+ else:
+ new_result = obj
+
+ if result_key is None:
+ return new_result
+ return result_key, new_result
+
+
+def character_backward_compatibility_hook(item, parents, result,
+ *args, **kwargs):
+ """Previously, Fortran character was incorrectly treated as
+ character*1. This hook fixes the usage of the corresponding
+ variables in `check`, `dimension`, `=`, and `callstatement`
+ expressions.
+
+ The usage of `char*` in `callprotoargument` expression can be left
+ unchanged because C `character` is C typedef of `char`, although,
+ new implementations should use `character*` in the corresponding
+ expressions.
+
+ See https://github.com/numpy/numpy/pull/19388 for more information.
+
+ """
+ parent_key, parent_value = parents[-1]
+ key, value = item
+
+ def fix_usage(varname, value):
+ value = re.sub(r'[*]\s*\b' + varname + r'\b', varname, value)
+ value = re.sub(r'\b' + varname + r'\b\s*[\[]\s*0\s*[\]]',
+ varname, value)
+ return value
+
+ if parent_key in ['dimension', 'check']:
+ assert parents[-3][0] == 'vars'
+ vars_dict = parents[-3][1]
+ elif key == '=':
+ assert parents[-2][0] == 'vars'
+ vars_dict = parents[-2][1]
+ else:
+ vars_dict = None
+
+ new_value = None
+ if vars_dict is not None:
+ new_value = value
+ for varname, vd in vars_dict.items():
+ if ischaracter(vd):
+ new_value = fix_usage(varname, new_value)
+ elif key == 'callstatement':
+ vars_dict = parents[-2][1]['vars']
+ new_value = value
+ for varname, vd in vars_dict.items():
+ if ischaracter(vd):
+ # replace all occurrences of `<varname>` with
+ # `&<varname>` in argument passing
+ new_value = re.sub(
+ r'(?<![&])\b' + varname + r'\b', '&' + varname, new_value)
+
+ if new_value is not None:
+ if new_value != value:
+ # We report the replacements here so that downstream
+ # software could update their source codes
+ # accordingly. However, such updates are recommended only
+ # when BC with numpy 1.21 or older is not required.
+ outmess(f'character_bc_hook[{parent_key}.{key}]:'
+ f' replaced `{value}` -> `{new_value}`\n', 1)
+ return (key, new_value)
+
+
+post_processing_hooks.append(character_backward_compatibility_hook)
+
+
if __name__ == "__main__":
files = []
funcs = []
@@ -3341,17 +3517,18 @@ if __name__ == "__main__":
funcs.append(l)
if not strictf77 and f77modulename and not skipemptyends:
outmess("""\
- Warning: You have specified module name for non Fortran 77 code
- that should not need one (expect if you are scanning F90 code
- for non module blocks but then you should use flag -skipemptyends
- and also be sure that the files do not contain programs without program statement).
+ Warning: You have specified module name for non Fortran 77 code that
+ should not need one (expect if you are scanning F90 code for non
+ module blocks but then you should use flag -skipemptyends and also
+ be sure that the files do not contain programs without program
+ statement).
""", 0)
postlist = crackfortran(files)
if pyffilename:
outmess('Writing fortran code to file %s\n' % repr(pyffilename), 0)
pyf = crack2fortran(postlist)
- with open(pyffilename, 'w') as f:
+ with open(pyffilename, 'w') as f:
f.write(pyf)
if showblocklist:
show(postlist)
diff --git a/numpy/f2py/f90mod_rules.py b/numpy/f2py/f90mod_rules.py
index 3e1c9674f..a3bb6a212 100644
--- a/numpy/f2py/f90mod_rules.py
+++ b/numpy/f2py/f90mod_rules.py
@@ -147,17 +147,9 @@ def buildhooks(pymod):
if not dms:
dms = '-1'
use_fgetdims2 = fgetdims2
- if isstringarray(var):
- if 'charselector' in var and 'len' in var['charselector']:
- cadd('\t{"%s",%s,{{%s,%s}},%s},'
- % (undo_rmbadname1(n), dm['rank'], dms, var['charselector']['len'], at))
- use_fgetdims2 = fgetdims2_sa
- else:
- cadd('\t{"%s",%s,{{%s}},%s},' %
- (undo_rmbadname1(n), dm['rank'], dms, at))
- else:
- cadd('\t{"%s",%s,{{%s}},%s},' %
- (undo_rmbadname1(n), dm['rank'], dms, at))
+ cadd('\t{"%s",%s,{{%s}},%s, %s},' %
+ (undo_rmbadname1(n), dm['rank'], dms, at,
+ capi_maps.get_elsize(var)))
dadd('\\item[]{{}\\verb@%s@{}}' %
(capi_maps.getarrdocsign(n, var)))
if hasnote(var):
@@ -216,8 +208,10 @@ def buildhooks(pymod):
ar['docs'] = []
ar['docshort'] = []
ret = dictappend(ret, ar)
- cadd('\t{"%s",-1,{{-1}},0,NULL,(void *)f2py_rout_#modulename#_%s_%s,doc_f2py_rout_#modulename#_%s_%s},' %
- (b['name'], m['name'], b['name'], m['name'], b['name']))
+ cadd(('\t{"%s",-1,{{-1}},0,0,NULL,(void *)'
+ 'f2py_rout_#modulename#_%s_%s,'
+ 'doc_f2py_rout_#modulename#_%s_%s},')
+ % (b['name'], m['name'], b['name'], m['name'], b['name']))
sargs.append('char *%s' % (b['name']))
sargsp.append('char *')
iadd('\tf2py_%s_def[i_f2py++].data = %s;' %
diff --git a/numpy/f2py/func2subr.py b/numpy/f2py/func2subr.py
index 21d4c009c..2a05f065b 100644
--- a/numpy/f2py/func2subr.py
+++ b/numpy/f2py/func2subr.py
@@ -13,10 +13,6 @@ $Date: 2004/11/26 11:13:06 $
Pearu Peterson
"""
-__version__ = "$Revision: 1.16 $"[10:-1]
-
-f2py_version = 'See `f2py -v`'
-
import copy
from .auxfuncs import (
@@ -108,15 +104,19 @@ def createfuncwrapper(rout, signature=0):
else:
args = [newname] + rout['args']
- l = var2fixfortran(vars, name, newname, f90mode)
- if l[:13] == 'character*(*)':
+ l_tmpl = var2fixfortran(vars, name, '@@@NAME@@@', f90mode)
+ if l_tmpl[:13] == 'character*(*)':
if f90mode:
- l = 'character(len=10)' + l[13:]
+ l_tmpl = 'character(len=10)' + l_tmpl[13:]
else:
- l = 'character*10' + l[13:]
+ l_tmpl = 'character*10' + l_tmpl[13:]
charselect = vars[name]['charselector']
if charselect.get('*', '') == '(*)':
charselect['*'] = '10'
+
+ l1 = l_tmpl.replace('@@@NAME@@@', newname)
+ rl = None
+
sargs = ', '.join(args)
if f90mode:
add('subroutine f2pywrap_%s_%s (%s)' %
@@ -127,7 +127,8 @@ def createfuncwrapper(rout, signature=0):
add('subroutine f2pywrap%s (%s)' % (name, sargs))
if not need_interface:
add('external %s' % (fortranname))
- l = l + ', ' + fortranname
+ rl = l_tmpl.replace('@@@NAME@@@', '') + ' ' + fortranname
+
if need_interface:
for line in rout['saved_interface'].split('\n'):
if line.lstrip().startswith('use ') and '__user__' not in line:
@@ -156,7 +157,9 @@ def createfuncwrapper(rout, signature=0):
continue
add(var2fixfortran(vars, a, f90mode=f90mode))
- add(l)
+ add(l1)
+ if rl is not None:
+ add(rl)
if need_interface:
if f90mode:
@@ -293,8 +296,8 @@ def assubr(rout):
if issubroutine_wrap(rout):
fortranname = getfortranname(rout)
name = rout['name']
- outmess('\t\tCreating wrapper for Fortran subroutine "%s"("%s")...\n' % (
- name, fortranname))
+ outmess('\t\tCreating wrapper for Fortran subroutine "%s"("%s")...\n'
+ % (name, fortranname))
rout = copy.copy(rout)
return rout, createsubrwrapper(rout)
return rout, ''
diff --git a/numpy/f2py/rules.py b/numpy/f2py/rules.py
index c56225032..c9c3b2383 100755
--- a/numpy/f2py/rules.py
+++ b/numpy/f2py/rules.py
@@ -57,22 +57,23 @@ from pathlib import Path
# __version__.version is now the same as the NumPy version
from . import __version__
-f2py_version = __version__.version
-numpy_version = __version__.version
from .auxfuncs import (
applyrules, debugcapi, dictappend, errmess, gentitle, getargs2,
- hascallstatement, hasexternals, hasinitvalue, hasnote, hasresultnote,
- isarray, isarrayofstrings, iscomplex, iscomplexarray,
- iscomplexfunction, iscomplexfunction_warn, isdummyroutine, isexternal,
- isfunction, isfunction_wrap, isint1array, isintent_aux, isintent_c,
- isintent_callback, isintent_copy, isintent_hide, isintent_inout,
- isintent_nothide, isintent_out, isintent_overwrite, islogical,
- islong_complex, islong_double, islong_doublefunction, islong_long,
- islong_longfunction, ismoduleroutine, isoptional, isrequired, isscalar,
- issigned_long_longarray, isstring, isstringarray, isstringfunction,
- issubroutine, issubroutine_wrap, isthreadsafe, isunsigned,
- isunsigned_char, isunsigned_chararray, isunsigned_long_long,
+ hascallstatement, hasexternals, hasinitvalue, hasnote,
+ hasresultnote, isarray, isarrayofstrings, ischaracter,
+ ischaracterarray, ischaracter_or_characterarray, iscomplex,
+ iscomplexarray, iscomplexfunction, iscomplexfunction_warn,
+ isdummyroutine, isexternal, isfunction, isfunction_wrap, isint1,
+ isint1array, isintent_aux, isintent_c, isintent_callback,
+ isintent_copy, isintent_hide, isintent_inout, isintent_nothide,
+ isintent_out, isintent_overwrite, islogical, islong_complex,
+ islong_double, islong_doublefunction, islong_long,
+ islong_longfunction, ismoduleroutine, isoptional, isrequired,
+ isscalar, issigned_long_longarray, isstring, isstringarray,
+ isstringfunction, issubroutine,
+ issubroutine_wrap, isthreadsafe, isunsigned, isunsigned_char,
+ isunsigned_chararray, isunsigned_long_long,
isunsigned_long_longarray, isunsigned_short, isunsigned_shortarray,
l_and, l_not, l_or, outmess, replace, stripcomma, requiresf90wrapper
)
@@ -84,9 +85,12 @@ from . import use_rules
from . import f90mod_rules
from . import func2subr
+f2py_version = __version__.version
+numpy_version = __version__.version
+
options = {}
sepdict = {}
-#for k in ['need_cfuncs']: sepdict[k]=','
+# for k in ['need_cfuncs']: sepdict[k]=','
for k in ['decl',
'frompyobj',
'cleanupfrompyobj',
@@ -405,10 +409,18 @@ rout_rules = [
ismoduleroutine: '',
isdummyroutine: ''
},
- 'routine_def': {l_not(l_or(ismoduleroutine, isintent_c, isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#F_FUNC#(#fortranname#,#FORTRANNAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
- l_and(l_not(ismoduleroutine), isintent_c, l_not(isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#fortranname#,(f2py_init_func)#apiname#,doc_#apiname#},',
- l_and(l_not(ismoduleroutine), isdummyroutine): ' {\"#name#\",-1,{{-1}},0,NULL,(f2py_init_func)#apiname#,doc_#apiname#},',
- },
+ 'routine_def': {
+ l_not(l_or(ismoduleroutine, isintent_c, isdummyroutine)):
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)'
+ ' #F_FUNC#(#fortranname#,#FORTRANNAME#),'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ l_and(l_not(ismoduleroutine), isintent_c, l_not(isdummyroutine)):
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)#fortranname#,'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ l_and(l_not(ismoduleroutine), isdummyroutine):
+ ' {\"#name#\",-1,{{-1}},0,0,NULL,'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ },
'need': {l_and(l_not(l_or(ismoduleroutine, isintent_c)), l_not(isdummyroutine)): 'F_FUNC'},
'callfortranroutine': [
{debugcapi: [
@@ -432,9 +444,15 @@ rout_rules = [
isdummyroutine: '',
},
- 'routine_def': {l_not(l_or(ismoduleroutine, isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#F_WRAPPEDFUNC#(#name_lower#,#NAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
- isdummyroutine: ' {\"#name#\",-1,{{-1}},0,NULL,(f2py_init_func)#apiname#,doc_#apiname#},',
- },
+ 'routine_def': {
+ l_not(l_or(ismoduleroutine, isdummyroutine)):
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)'
+ ' #F_WRAPPEDFUNC#(#name_lower#,#NAME#),'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ isdummyroutine:
+ ' {\"#name#\",-1,{{-1}},0,0,NULL,'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ },
'initf2pywraphook': {l_not(l_or(ismoduleroutine, isdummyroutine)): '''
{
extern #ctype# #F_FUNC#(#name_lower#,#NAME#)(void);
@@ -470,9 +488,15 @@ rout_rules = [
isdummyroutine: '',
},
- 'routine_def': {l_not(l_or(ismoduleroutine, isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#F_WRAPPEDFUNC#(#name_lower#,#NAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
- isdummyroutine: ' {\"#name#\",-1,{{-1}},0,NULL,(f2py_init_func)#apiname#,doc_#apiname#},',
- },
+ 'routine_def': {
+ l_not(l_or(ismoduleroutine, isdummyroutine)):
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)'
+ ' #F_WRAPPEDFUNC#(#name_lower#,#NAME#),'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ isdummyroutine:
+ ' {\"#name#\",-1,{{-1}},0,0,NULL,'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},',
+ },
'initf2pywraphook': {l_not(l_or(ismoduleroutine, isdummyroutine)): '''
{
extern void #F_FUNC#(#name_lower#,#NAME#)(void);
@@ -525,10 +549,19 @@ rout_rules = [
l_and(l_not(ismoduleroutine), isintent_c, l_not(isdummyroutine)): 'extern #ctype# #fortranname#(#callprotoargument#);',
isdummyroutine: ''
},
- 'routine_def': {l_and(l_not(l_or(ismoduleroutine, isintent_c)), l_not(isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#F_FUNC#(#fortranname#,#FORTRANNAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
- l_and(l_not(ismoduleroutine), isintent_c, l_not(isdummyroutine)): ' {\"#name#\",-1,{{-1}},0,(char *)#fortranname#,(f2py_init_func)#apiname#,doc_#apiname#},',
- isdummyroutine: ' {\"#name#\",-1,{{-1}},0,NULL,(f2py_init_func)#apiname#,doc_#apiname#},',
- },
+ 'routine_def': {
+ l_and(l_not(l_or(ismoduleroutine, isintent_c)),
+ l_not(isdummyroutine)):
+ (' {\"#name#\",-1,{{-1}},0,0,(char *)'
+ ' #F_FUNC#(#fortranname#,#FORTRANNAME#),'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},'),
+ l_and(l_not(ismoduleroutine), isintent_c, l_not(isdummyroutine)):
+ (' {\"#name#\",-1,{{-1}},0,0,(char *)#fortranname#,'
+ ' (f2py_init_func)#apiname#,doc_#apiname#},'),
+ isdummyroutine:
+ ' {\"#name#\",-1,{{-1}},0,0,NULL,'
+ '(f2py_init_func)#apiname#,doc_#apiname#},',
+ },
'decl': [{iscomplexfunction_warn: ' #ctype# #name#_return_value={0,0};',
l_not(iscomplexfunction): ' #ctype# #name#_return_value=0;'},
{iscomplexfunction:
@@ -562,9 +595,9 @@ rout_rules = [
}, { # String function # in use for --no-wrap
'declfortranroutine': 'extern void #F_FUNC#(#fortranname#,#FORTRANNAME#)(#callprotoargument#);',
'routine_def': {l_not(l_or(ismoduleroutine, isintent_c)):
- ' {\"#name#\",-1,{{-1}},0,(char *)#F_FUNC#(#fortranname#,#FORTRANNAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)#F_FUNC#(#fortranname#,#FORTRANNAME#),(f2py_init_func)#apiname#,doc_#apiname#},',
l_and(l_not(ismoduleroutine), isintent_c):
- ' {\"#name#\",-1,{{-1}},0,(char *)#fortranname#,(f2py_init_func)#apiname#,doc_#apiname#},'
+ ' {\"#name#\",-1,{{-1}},0,0,(char *)#fortranname#,(f2py_init_func)#apiname#,doc_#apiname#},'
},
'decl': [' #ctype# #name#_return_value = NULL;',
' int #name#_return_value_len = 0;'],
@@ -623,6 +656,8 @@ typedef_need_dict = {islong_long: 'long_long',
isunsigned_shortarray: 'unsigned_short',
isunsigned_long_longarray: 'unsigned_long_long',
issigned_long_longarray: 'long_long',
+ isint1: 'signed_char',
+ ischaracter_or_characterarray: 'character',
}
aux_rules = [
@@ -685,7 +720,7 @@ aux_rules = [
},
# Integer*-1 array
{'need': '#ctype#',
- '_check': isunsigned_chararray,
+ '_check': l_or(isunsigned_chararray, isunsigned_char),
'_depend': ''
},
# Integer*-2 array
@@ -856,7 +891,8 @@ if (#varname#_cb.capi==Py_None) {
if (f2py_success) {"""},
'closepyobjfrom': {isintent_inout: " } /*if (f2py_success) of #varname# pyobjfrom*/"},
'need': {isintent_inout: 'try_pyarr_from_#ctype#'},
- '_check': l_and(isscalar, l_not(iscomplex), isintent_nothide)
+ '_check': l_and(isscalar, l_not(iscomplex), l_not(isstring),
+ isintent_nothide)
}, {
'frompyobj': [
# hasinitvalue...
@@ -963,11 +999,11 @@ if (#varname#_cb.capi==Py_None) {
'return': {isintent_out: ',#varname#'},
'need': ['len..',
{l_and(isintent_out, l_not(isintent_c)): 'STRINGPADN'}],
- '_check':isstring
+ '_check': isstring
}, { # Common
'frompyobj': [
"""\
- slen(#varname#) = #length#;
+ slen(#varname#) = #elsize#;
f2py_success = #ctype#_from_pyobj(&#varname#,&slen(#varname#),#init#,"""
"""#varname#_capi,\"#ctype#_from_pyobj failed in converting #nth#"""
"""`#varname#\' of #pyname# to C #ctype#\");
@@ -1011,11 +1047,13 @@ if (#varname#_cb.capi==Py_None) {
'decl': [' #ctype# *#varname# = NULL;',
' npy_intp #varname#_Dims[#rank#] = {#rank*[-1]#};',
' const int #varname#_Rank = #rank#;',
- ' PyArrayObject *capi_#varname#_tmp = NULL;',
+ ' PyArrayObject *capi_#varname#_as_array = NULL;',
' int capi_#varname#_intent = 0;',
+ {isstringarray: ' int slen(#varname#) = 0;'},
],
'callfortran':'#varname#,',
- 'return':{isintent_out: ',capi_#varname#_tmp'},
+ 'callfortranappend': {isstringarray: 'slen(#varname#),'},
+ 'return': {isintent_out: ',capi_#varname#_as_array'},
'need': 'len..',
'_check': isarray
}, { # intent(overwrite) array
@@ -1057,36 +1095,48 @@ if (#varname#_cb.capi==Py_None) {
'keys_capi': {isoptional: ',&#varname#_capi'},
'_check': l_and(isarray, isintent_nothide)
}, {
- 'frompyobj': [' #setdims#;',
- ' capi_#varname#_intent |= #intent#;',
- {isintent_hide:
- ' capi_#varname#_tmp = array_from_pyobj(#atype#,#varname#_Dims,#varname#_Rank,capi_#varname#_intent,Py_None);'},
- {isintent_nothide:
- ' capi_#varname#_tmp = array_from_pyobj(#atype#,#varname#_Dims,#varname#_Rank,capi_#varname#_intent,#varname#_capi);'},
- """\
- if (capi_#varname#_tmp == NULL) {
- PyObject *exc, *val, *tb;
- PyErr_Fetch(&exc, &val, &tb);
- PyErr_SetString(exc ? exc : #modulename#_error,\"failed in converting #nth# `#varname#\' of #pyname# to C/Fortran array\" );
- npy_PyErr_ChainExceptionsCause(exc, val, tb);
+ 'frompyobj': [
+ ' #setdims#;',
+ ' capi_#varname#_intent |= #intent#;',
+ (' const char * capi_errmess = "#modulename#.#pyname#:'
+ ' failed to create array from the #nth# `#varname#`";'),
+ {isintent_hide:
+ ' capi_#varname#_as_array = ndarray_from_pyobj('
+ ' #atype#,#elsize#,#varname#_Dims,#varname#_Rank,'
+ ' capi_#varname#_intent,Py_None,capi_errmess);'},
+ {isintent_nothide:
+ ' capi_#varname#_as_array = ndarray_from_pyobj('
+ ' #atype#,#elsize#,#varname#_Dims,#varname#_Rank,'
+ ' capi_#varname#_intent,#varname#_capi,capi_errmess);'},
+ """\
+ if (capi_#varname#_as_array == NULL) {
+ PyObject* capi_err = PyErr_Occurred();
+ if (capi_err == NULL) {
+ capi_err = #modulename#_error;
+ PyErr_SetString(capi_err, capi_errmess);
+ }
} else {
- #varname# = (#ctype# *)(PyArray_DATA(capi_#varname#_tmp));
+ #varname# = (#ctype# *)(PyArray_DATA(capi_#varname#_as_array));
""",
- {hasinitvalue: [
- {isintent_nothide:
- ' if (#varname#_capi == Py_None) {'},
- {isintent_hide: ' {'},
- {iscomplexarray: ' #ctype# capi_c;'},
- """\
+ {isstringarray:
+ ' slen(#varname#) = f2py_itemsize(#varname#);'},
+ {hasinitvalue: [
+ {isintent_nothide:
+ ' if (#varname#_capi == Py_None) {'},
+ {isintent_hide: ' {'},
+ {iscomplexarray: ' #ctype# capi_c;'},
+ """\
int *_i,capi_i=0;
CFUNCSMESS(\"#name#: Initializing #varname#=#init#\\n\");
- if (initforcomb(PyArray_DIMS(capi_#varname#_tmp),PyArray_NDIM(capi_#varname#_tmp),1)) {
+ if (initforcomb(PyArray_DIMS(capi_#varname#_as_array),
+ PyArray_NDIM(capi_#varname#_as_array),1)) {
while ((_i = nextforcomb()))
#varname#[capi_i++] = #init#; /* fortran way */
} else {
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
- PyErr_SetString(exc ? exc : #modulename#_error,\"Initialization of #nth# #varname# failed (initforcomb).\");
+ PyErr_SetString(exc ? exc : #modulename#_error,
+ \"Initialization of #nth# #varname# failed (initforcomb).\");
npy_PyErr_ChainExceptionsCause(exc, val, tb);
f2py_success = 0;
}
@@ -1094,12 +1144,13 @@ if (#varname#_cb.capi==Py_None) {
if (f2py_success) {"""]},
],
'cleanupfrompyobj': [ # note that this list will be reversed
- ' } /*if (capi_#varname#_tmp == NULL) ... else of #varname#*/',
+ ' } '
+ '/* if (capi_#varname#_as_array == NULL) ... else of #varname# */',
{l_not(l_or(isintent_out, isintent_hide)): """\
- if((PyObject *)capi_#varname#_tmp!=#varname#_capi) {
- Py_XDECREF(capi_#varname#_tmp); }"""},
+ if((PyObject *)capi_#varname#_as_array!=#varname#_capi) {
+ Py_XDECREF(capi_#varname#_as_array); }"""},
{l_and(isintent_hide, l_not(isintent_out))
- : """ Py_XDECREF(capi_#varname#_tmp);"""},
+ : """ Py_XDECREF(capi_#varname#_as_array);"""},
{hasinitvalue: ' } /*if (f2py_success) of #varname# init*/'},
],
'_check': isarray,
@@ -1136,6 +1187,16 @@ if (#varname#_cb.capi==Py_None) {
'_check': iscomplexarray,
'_depend': ''
},
+ # Character
+ {
+ 'need': 'string',
+ '_check': ischaracter,
+ },
+ # Character array
+ {
+ 'need': 'string',
+ '_check': ischaracterarray,
+ },
# Stringarray
{
'callfortranappend': {isarrayofstrings: 'flen(#varname#),'},
diff --git a/numpy/f2py/src/fortranobject.c b/numpy/f2py/src/fortranobject.c
index c96378170..8282655e5 100644
--- a/numpy/f2py/src/fortranobject.c
+++ b/numpy/f2py/src/fortranobject.c
@@ -5,6 +5,7 @@
extern "C" {
#endif
+#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@@ -101,6 +102,20 @@ F2PyGetThreadLocalCallbackPtr(char *key)
return prev;
}
+static PyArray_Descr *
+get_descr_from_type_and_elsize(const int type_num, const int elsize) {
+ PyArray_Descr * descr = PyArray_DescrFromType(type_num);
+ if (type_num == NPY_STRING) {
+ // PyArray_DescrFromType returns descr with elsize = 0.
+ PyArray_DESCR_REPLACE(descr);
+ if (descr == NULL) {
+ return NULL;
+ }
+ descr->elsize = elsize;
+ }
+ return descr;
+}
+
/************************* FortranObject *******************************/
typedef PyObject *(*fortranfunc)(PyObject *, PyObject *, PyObject *, void *);
@@ -141,18 +156,17 @@ PyFortranObject_New(FortranDataDef *defs, f2py_void_func init)
}
else if ((fp->defs[i].data) !=
NULL) { /* Is Fortran variable or array (not allocatable) */
- if (fp->defs[i].type == NPY_STRING) {
- npy_intp n = fp->defs[i].rank - 1;
- v = PyArray_New(&PyArray_Type, n, fp->defs[i].dims.d,
- NPY_STRING, NULL, fp->defs[i].data,
- fp->defs[i].dims.d[n], NPY_ARRAY_FARRAY, NULL);
- }
- else {
- v = PyArray_New(&PyArray_Type, fp->defs[i].rank,
- fp->defs[i].dims.d, fp->defs[i].type, NULL,
- fp->defs[i].data, 0, NPY_ARRAY_FARRAY, NULL);
+ PyArray_Descr *
+ descr = get_descr_from_type_and_elsize(fp->defs[i].type,
+ fp->defs[i].elsize);
+ if (descr == NULL) {
+ goto fail;
}
+ v = PyArray_NewFromDescr(&PyArray_Type, descr, fp->defs[i].rank,
+ fp->defs[i].dims.d, NULL, fp->defs[i].data,
+ NPY_ARRAY_FARRAY, NULL);
if (v == NULL) {
+ Py_DECREF(descr);
goto fail;
}
PyDict_SetItemString(fp->dict, fp->defs[i].name, v);
@@ -178,6 +192,13 @@ PyFortranObject_NewAsAttr(FortranDataDef *defs)
}
fp->len = 1;
fp->defs = defs;
+ if (defs->rank == -1) {
+ PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("function %s", defs->name));
+ } else if (defs->rank == 0) {
+ PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("scalar %s", defs->name));
+ } else {
+ PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("array %s", defs->name));
+ }
return (PyObject *)fp;
}
@@ -697,9 +718,10 @@ f2py_report_on_array_copy_fromany(void)
* $Id: fortranobject.c,v 1.52 2005/07/11 07:44:20 pearu Exp $
*/
-static int
-check_and_fix_dimensions(const PyArrayObject *arr, const int rank,
- npy_intp *dims);
+static int check_and_fix_dimensions(const PyArrayObject* arr,
+ const int rank,
+ npy_intp *dims,
+ const char *errmess);
static int
find_first_negative_dimension(const int rank, const npy_intp *dims)
@@ -762,80 +784,176 @@ swap_arrays(PyArrayObject *obj1, PyArrayObject *obj2)
return 0;
}
-#define ARRAY_ISCOMPATIBLE(arr, type_num) \
- ((PyArray_ISINTEGER(arr) && PyTypeNum_ISINTEGER(type_num)) || \
- (PyArray_ISFLOAT(arr) && PyTypeNum_ISFLOAT(type_num)) || \
- (PyArray_ISCOMPLEX(arr) && PyTypeNum_ISCOMPLEX(type_num)) || \
- (PyArray_ISBOOL(arr) && PyTypeNum_ISBOOL(type_num)))
+#define ARRAY_ISCOMPATIBLE(arr,type_num) \
+ ((PyArray_ISINTEGER(arr) && PyTypeNum_ISINTEGER(type_num)) || \
+ (PyArray_ISFLOAT(arr) && PyTypeNum_ISFLOAT(type_num)) || \
+ (PyArray_ISCOMPLEX(arr) && PyTypeNum_ISCOMPLEX(type_num)) || \
+ (PyArray_ISBOOL(arr) && PyTypeNum_ISBOOL(type_num)) || \
+ (PyArray_ISSTRING(arr) && PyTypeNum_ISSTRING(type_num)))
+
+static int
+get_elsize(PyObject *obj) {
+ /*
+ get_elsize determines array itemsize from a Python object. Returns
+ elsize if succesful, -1 otherwise.
+
+ Supported types of the input are: numpy.ndarray, bytes, str, tuple,
+ list.
+ */
+
+ if (PyArray_Check(obj)) {
+ return PyArray_DESCR((PyArrayObject *)obj)->elsize;
+ } else if (PyBytes_Check(obj)) {
+ return PyBytes_GET_SIZE(obj);
+ } else if (PyUnicode_Check(obj)) {
+ return PyUnicode_GET_LENGTH(obj);
+ } else if (PySequence_Check(obj)) {
+ PyObject* fast = PySequence_Fast(obj, "f2py:fortranobject.c:get_elsize");
+ if (fast != NULL) {
+ Py_ssize_t i, n = PySequence_Fast_GET_SIZE(fast);
+ int sz, elsize = 0;
+ for (i=0; i<n; i++) {
+ sz = get_elsize(PySequence_Fast_GET_ITEM(fast, i) /* borrowed */);
+ if (sz > elsize) {
+ elsize = sz;
+ }
+ }
+ Py_DECREF(fast);
+ return elsize;
+ }
+ }
+ return -1;
+}
extern PyArrayObject *
-array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
- const int intent, PyObject *obj)
-{
+ndarray_from_pyobj(const int type_num,
+ const int elsize_,
+ npy_intp *dims,
+ const int rank,
+ const int intent,
+ PyObject *obj,
+ const char *errmess) {
/*
+ * Return an array with given element type and shape from a Python
+ * object while taking into account the usage intent of the array.
+ *
+ * - element type is defined by type_num and elsize
+ * - shape is defined by dims and rank
+ *
+ * ndarray_from_pyobj is used to convert Python object arguments
+ * to numpy ndarrays with given type and shape that data is passed
+ * to interfaced Fortran or C functions.
+ *
+ * errmess (if not NULL), contains a prefix of an error message
+ * for an exception to be triggered within this function.
+ *
+ * Negative elsize value means that elsize is to be determined
+ * from the Python object in runtime.
+ *
+ * Note on strings
+ * ---------------
+ *
+ * String type (type_num == NPY_STRING) does not have fixed
+ * element size and, by default, the type object sets it to
+ * 0. Therefore, for string types, one has to use elsize
+ * argument. For other types, elsize value is ignored.
+ *
+ * NumPy defines the type of a fixed-width string as
+ * dtype('S<width>'). In addition, there is also dtype('c'), that
+ * appears as dtype('S1') (these have the same type_num value),
+ * but is actually different (.char attribute is either 'S' or
+ * 'c', respecitely).
+ *
+ * In Fortran, character arrays and strings are different
+ * concepts. The relation between Fortran types, NumPy dtypes,
+ * and type_num-elsize pairs, is defined as follows:
+ *
+ * character*5 foo | dtype('S5') | elsize=5, shape=()
+ * character(5) foo | dtype('S1') | elsize=1, shape=(5)
+ * character*5 foo(n) | dtype('S5') | elsize=5, shape=(n,)
+ * character(5) foo(n) | dtype('S1') | elsize=1, shape=(5, n)
+ * character*(*) foo | dtype('S') | elsize=-1, shape=()
+ *
* Note about reference counting
- * -----------------------------
+ * -----------------------------
+ *
* If the caller returns the array to Python, it must be done with
- * Py_BuildValue("N",arr).
- * Otherwise, if obj!=arr then the caller must call Py_DECREF(arr).
+ * Py_BuildValue("N",arr). Otherwise, if obj!=arr then the caller
+ * must call Py_DECREF(arr).
*
* Note on intent(cache,out,..)
- * ---------------------
+ * ----------------------------
* Don't expect correct data when returning intent(cache) array.
*
*/
- char mess[200];
+ char mess[F2PY_MESSAGE_BUFFER_SIZE];
PyArrayObject *arr = NULL;
- PyArray_Descr *descr;
- char typechar;
- int elsize;
-
- if ((intent & F2PY_INTENT_HIDE) ||
- ((intent & F2PY_INTENT_CACHE) && (obj == Py_None)) ||
- ((intent & F2PY_OPTIONAL) && (obj == Py_None))) {
+ int elsize = (elsize_ < 0 ? get_elsize(obj) : elsize_);
+ if (elsize < 0) {
+ if (errmess != NULL) {
+ strcpy(mess, errmess);
+ }
+ sprintf(mess + strlen(mess),
+ " -- failed to determine element size from %s",
+ Py_TYPE(obj)->tp_name);
+ PyErr_SetString(PyExc_SystemError, mess);
+ return NULL;
+ }
+ PyArray_Descr * descr = get_descr_from_type_and_elsize(type_num, elsize); // new reference
+ if (descr == NULL) {
+ return NULL;
+ }
+ elsize = descr->elsize;
+ if ((intent & F2PY_INTENT_HIDE)
+ || ((intent & F2PY_INTENT_CACHE) && (obj == Py_None))
+ || ((intent & F2PY_OPTIONAL) && (obj == Py_None))
+ ) {
/* intent(cache), optional, intent(hide) */
- int i = find_first_negative_dimension(rank, dims);
- if (i >= 0) {
- PyErr_Format(PyExc_ValueError,
- "failed to create intent(cache|hide)|optional array"
- " -- must have defined dimensions, but dims[%d] = %"
- NPY_INTP_FMT, i, dims[i]);
+ int ineg = find_first_negative_dimension(rank, dims);
+ if (ineg >= 0) {
+ int i;
+ strcpy(mess, "failed to create intent(cache|hide)|optional array"
+ "-- must have defined dimensions but got (");
+ for(i = 0; i < rank; ++i)
+ sprintf(mess + strlen(mess), "%" NPY_INTP_FMT ",", dims[i]);
+ strcat(mess, ")");
+ PyErr_SetString(PyExc_ValueError, mess);
+ Py_DECREF(descr);
return NULL;
}
- arr = (PyArrayObject *)PyArray_New(&PyArray_Type, rank, dims, type_num,
- NULL, NULL, 1,
- !(intent & F2PY_INTENT_C), NULL);
- if (arr == NULL)
- return NULL;
- if (!(intent & F2PY_INTENT_CACHE))
- PyArray_FILLWBYTE(arr, 0);
+ arr = (PyArrayObject *) \
+ PyArray_NewFromDescr(&PyArray_Type, descr, rank, dims,
+ NULL, NULL, !(intent & F2PY_INTENT_C), NULL);
+ if (arr == NULL) {
+ Py_DECREF(descr);
+ return NULL;
+ }
+ if (PyArray_ITEMSIZE(arr) != elsize) {
+ strcpy(mess, "failed to create intent(cache|hide)|optional array");
+ sprintf(mess+strlen(mess)," -- expected elsize=%d got %" NPY_INTP_FMT, elsize, (npy_intp)PyArray_ITEMSIZE(arr));
+ PyErr_SetString(PyExc_ValueError,mess);
+ Py_DECREF(arr);
+ return NULL;
+ }
+ if (!(intent & F2PY_INTENT_CACHE)) {
+ PyArray_FILLWBYTE(arr, 0);
+ }
return arr;
}
- descr = PyArray_DescrFromType(type_num);
- /* compatibility with NPY_CHAR */
- if (type_num == NPY_STRING) {
- PyArray_DESCR_REPLACE(descr);
- if (descr == NULL) {
- return NULL;
- }
- descr->elsize = 1;
- descr->type = NPY_CHARLTR;
- }
- elsize = descr->elsize;
- typechar = descr->type;
- Py_DECREF(descr);
if (PyArray_Check(obj)) {
arr = (PyArrayObject *)obj;
-
if (intent & F2PY_INTENT_CACHE) {
/* intent(cache) */
- if (PyArray_ISONESEGMENT(arr) && PyArray_ITEMSIZE(arr) >= elsize) {
- if (check_and_fix_dimensions(arr, rank, dims)) {
- return NULL;
+ if (PyArray_ISONESEGMENT(arr)
+ && PyArray_ITEMSIZE(arr) >= elsize) {
+ if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
+ Py_DECREF(descr);
+ return NULL;
}
if (intent & F2PY_INTENT_OUT)
- Py_INCREF(arr);
+ Py_INCREF(arr);
+ Py_DECREF(descr);
return arr;
}
strcpy(mess, "failed to initialize intent(cache) array");
@@ -847,14 +965,16 @@ array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
"%" NPY_INTP_FMT,
elsize, (npy_intp)PyArray_ITEMSIZE(arr));
PyErr_SetString(PyExc_ValueError, mess);
+ Py_DECREF(descr);
return NULL;
}
/* here we have always intent(in) or intent(inout) or intent(inplace)
*/
- if (check_and_fix_dimensions(arr, rank, dims)) {
- return NULL;
+ if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
+ Py_DECREF(descr);
+ return NULL;
}
/*
printf("intent alignment=%d\n", F2PY_GET_ALIGNMENT(intent));
@@ -863,16 +983,18 @@ array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
for (i=1;i<=16;i++)
printf("i=%d isaligned=%d\n", i, ARRAY_ISALIGNED(arr, i));
*/
- if ((!(intent & F2PY_INTENT_COPY)) &&
+ if ((! (intent & F2PY_INTENT_COPY)) &&
PyArray_ITEMSIZE(arr) == elsize &&
- ARRAY_ISCOMPATIBLE(arr, type_num) &&
+ ARRAY_ISCOMPATIBLE(arr,type_num) &&
F2PY_CHECK_ALIGNMENT(arr, intent)) {
- if ((intent & F2PY_INTENT_C) ? PyArray_ISCARRAY_RO(arr)
- : PyArray_ISFARRAY_RO(arr)) {
+ if ((intent & F2PY_INTENT_INOUT || intent & F2PY_INTENT_INPLACE)
+ ? ((intent & F2PY_INTENT_C) ? PyArray_ISCARRAY(arr) : PyArray_ISFARRAY(arr))
+ : ((intent & F2PY_INTENT_C) ? PyArray_ISCARRAY_RO(arr) : PyArray_ISFARRAY_RO(arr))) {
if ((intent & F2PY_INTENT_OUT)) {
Py_INCREF(arr);
}
/* Returning input array */
+ Py_DECREF(descr);
return arr;
}
}
@@ -887,42 +1009,48 @@ array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
if (PyArray_ITEMSIZE(arr) != elsize)
sprintf(mess + strlen(mess),
" -- expected elsize=%d but got %" NPY_INTP_FMT,
- elsize, (npy_intp)PyArray_ITEMSIZE(arr));
- if (!(ARRAY_ISCOMPATIBLE(arr, type_num)))
+ elsize,
+ (npy_intp)PyArray_ITEMSIZE(arr)
+ );
+ if (!(ARRAY_ISCOMPATIBLE(arr, type_num))) {
sprintf(mess + strlen(mess),
" -- input '%c' not compatible to '%c'",
- PyArray_DESCR(arr)->type, typechar);
+ PyArray_DESCR(arr)->type, descr->type);
+ }
if (!(F2PY_CHECK_ALIGNMENT(arr, intent)))
sprintf(mess + strlen(mess), " -- input not %d-aligned",
F2PY_GET_ALIGNMENT(intent));
PyErr_SetString(PyExc_ValueError, mess);
+ Py_DECREF(descr);
return NULL;
}
/* here we have always intent(in) or intent(inplace) */
{
- PyArrayObject *retarr;
- retarr = (PyArrayObject *)PyArray_New(
- &PyArray_Type, PyArray_NDIM(arr), PyArray_DIMS(arr),
- type_num, NULL, NULL, 1, !(intent & F2PY_INTENT_C), NULL);
- if (retarr == NULL)
- return NULL;
- F2PY_REPORT_ON_ARRAY_COPY_FROMARR;
- if (PyArray_CopyInto(retarr, arr)) {
- Py_DECREF(retarr);
- return NULL;
- }
- if (intent & F2PY_INTENT_INPLACE) {
- if (swap_arrays(arr, retarr))
- return NULL; /* XXX: set exception */
- Py_XDECREF(retarr);
- if (intent & F2PY_INTENT_OUT)
- Py_INCREF(arr);
- }
- else {
- arr = retarr;
+ PyArrayObject * retarr = (PyArrayObject *) \
+ PyArray_NewFromDescr(&PyArray_Type, descr, PyArray_NDIM(arr), PyArray_DIMS(arr),
+ NULL, NULL, !(intent & F2PY_INTENT_C), NULL);
+ if (retarr==NULL) {
+ Py_DECREF(descr);
+ return NULL;
+ }
+ F2PY_REPORT_ON_ARRAY_COPY_FROMARR;
+ if (PyArray_CopyInto(retarr, arr)) {
+ Py_DECREF(retarr);
+ return NULL;
+ }
+ if (intent & F2PY_INTENT_INPLACE) {
+ if (swap_arrays(arr,retarr)) {
+ Py_DECREF(retarr);
+ return NULL; /* XXX: set exception */
}
+ Py_XDECREF(retarr);
+ if (intent & F2PY_INTENT_OUT)
+ Py_INCREF(arr);
+ } else {
+ arr = retarr;
+ }
}
return arr;
}
@@ -933,20 +1061,11 @@ array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
"failed to initialize intent(inout|inplace|cache) "
"array, input '%s' object is not an array",
Py_TYPE(obj)->tp_name);
+ Py_DECREF(descr);
return NULL;
}
{
- PyArray_Descr *descr = PyArray_DescrFromType(type_num);
- /* compatibility with NPY_CHAR */
- if (type_num == NPY_STRING) {
- PyArray_DESCR_REPLACE(descr);
- if (descr == NULL) {
- return NULL;
- }
- descr->elsize = 1;
- descr->type = NPY_CHARLTR;
- }
F2PY_REPORT_ON_ARRAY_COPY_FROMANY;
arr = (PyArrayObject *)PyArray_FromAny(
obj, descr, 0, 0,
@@ -954,22 +1073,54 @@ array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
: NPY_ARRAY_FARRAY) |
NPY_ARRAY_FORCECAST,
NULL);
- if (arr == NULL)
- return NULL;
- if (check_and_fix_dimensions(arr, rank, dims)) {
- return NULL;
+ // Warning: in the case of NPY_STRING, PyArray_FromAny may
+ // reset descr->elsize, e.g. dtype('S0') becomes dtype('S1').
+ if (arr == NULL) {
+ Py_DECREF(descr);
+ return NULL;
+ }
+ if (type_num != NPY_STRING && PyArray_ITEMSIZE(arr) != elsize) {
+ // This is internal sanity tests: elsize has been set to
+ // descr->elsize in the beginning of this function.
+ strcpy(mess, "failed to initialize intent(in) array");
+ sprintf(mess + strlen(mess),
+ " -- expected elsize=%d got %" NPY_INTP_FMT, elsize,
+ (npy_intp)PyArray_ITEMSIZE(arr));
+ PyErr_SetString(PyExc_ValueError, mess);
+ Py_DECREF(arr);
+ return NULL;
+ }
+ if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
+ Py_DECREF(arr);
+ return NULL;
}
return arr;
}
}
+extern PyArrayObject *
+array_from_pyobj(const int type_num,
+ npy_intp *dims,
+ const int rank,
+ const int intent,
+ PyObject *obj) {
+ /*
+ Same as ndarray_from_pyobj but with elsize determined from type,
+ if possible. Provided for backward compatibility.
+ */
+ PyArray_Descr* descr = PyArray_DescrFromType(type_num);
+ int elsize = descr->elsize;
+ Py_DECREF(descr);
+ return ndarray_from_pyobj(type_num, elsize, dims, rank, intent, obj, NULL);
+}
+
/*****************************************/
/* Helper functions for array_from_pyobj */
/*****************************************/
static int
-check_and_fix_dimensions(const PyArrayObject *arr, const int rank,
- npy_intp *dims)
+check_and_fix_dimensions(const PyArrayObject* arr, const int rank,
+ npy_intp *dims, const char *errmess)
{
/*
* This function fills in blanks (that are -1's) in dims list using
@@ -981,6 +1132,7 @@ check_and_fix_dimensions(const PyArrayObject *arr, const int rank,
* If an error condition is detected, an exception is set and 1 is
* returned.
*/
+ char mess[F2PY_MESSAGE_BUFFER_SIZE];
const npy_intp arr_size =
(PyArray_NDIM(arr)) ? PyArray_Size((PyObject *)arr) : 1;
#ifdef DEBUG_COPY_ND_ARRAY
@@ -1046,11 +1198,14 @@ check_and_fix_dimensions(const PyArrayObject *arr, const int rank,
d = PyArray_DIM(arr, i);
if (dims[i] >= 0) {
if (d > 1 && d != dims[i]) {
- PyErr_Format(
- PyExc_ValueError,
- "%d-th dimension must be fixed to %" NPY_INTP_FMT
- " but got %" NPY_INTP_FMT "\n",
+ if (errmess != NULL) {
+ strcpy(mess, errmess);
+ }
+ sprintf(mess + strlen(mess),
+ " -- %d-th dimension must be fixed to %"
+ NPY_INTP_FMT " but got %" NPY_INTP_FMT,
i, dims[i], d);
+ PyErr_SetString(PyExc_ValueError, mess);
return 1;
}
if (!dims[i])
@@ -1093,11 +1248,15 @@ check_and_fix_dimensions(const PyArrayObject *arr, const int rank,
d = PyArray_DIM(arr, j++);
if (dims[i] >= 0) {
if (d > 1 && d != dims[i]) {
- PyErr_Format(
- PyExc_ValueError,
- "%d-th dimension must be fixed to %" NPY_INTP_FMT
- " but got %" NPY_INTP_FMT " (real index=%d)\n",
- i, dims[i], d, j - 1);
+ if (errmess != NULL) {
+ strcpy(mess, errmess);
+ }
+ sprintf(mess + strlen(mess),
+ " -- %d-th dimension must be fixed to %"
+ NPY_INTP_FMT " but got %" NPY_INTP_FMT
+ " (real index=%d)\n",
+ i, dims[i], d, j-1);
+ PyErr_SetString(PyExc_ValueError, mess);
return 1;
}
if (!dims[i])
@@ -1161,6 +1320,72 @@ copy_ND_array(const PyArrayObject *arr, PyArrayObject *out)
return PyArray_CopyInto(out, (PyArrayObject *)arr);
}
+/********************* Various utility functions ***********************/
+
+extern int
+f2py_describe(PyObject *obj, char *buf) {
+ /*
+ Write the description of a Python object to buf. The caller must
+ provide buffer with size sufficient to write the description.
+
+ Return 1 on success.
+ */
+ char localbuf[F2PY_MESSAGE_BUFFER_SIZE];
+ if (PyBytes_Check(obj)) {
+ sprintf(localbuf, "%d-%s", (npy_int)PyBytes_GET_SIZE(obj), Py_TYPE(obj)->tp_name);
+ } else if (PyUnicode_Check(obj)) {
+ sprintf(localbuf, "%d-%s", (npy_int)PyUnicode_GET_LENGTH(obj), Py_TYPE(obj)->tp_name);
+ } else if (PyArray_CheckScalar(obj)) {
+ PyArrayObject* arr = (PyArrayObject*)obj;
+ sprintf(localbuf, "%c%" NPY_INTP_FMT "-%s-scalar", PyArray_DESCR(arr)->kind, PyArray_ITEMSIZE(arr), Py_TYPE(obj)->tp_name);
+ } else if (PyArray_Check(obj)) {
+ int i;
+ PyArrayObject* arr = (PyArrayObject*)obj;
+ strcpy(localbuf, "(");
+ for (i=0; i<PyArray_NDIM(arr); i++) {
+ if (i) {
+ strcat(localbuf, " ");
+ }
+ sprintf(localbuf + strlen(localbuf), "%" NPY_INTP_FMT ",", PyArray_DIM(arr, i));
+ }
+ sprintf(localbuf + strlen(localbuf), ")-%c%" NPY_INTP_FMT "-%s", PyArray_DESCR(arr)->kind, PyArray_ITEMSIZE(arr), Py_TYPE(obj)->tp_name);
+ } else if (PySequence_Check(obj)) {
+ sprintf(localbuf, "%d-%s", (npy_int)PySequence_Length(obj), Py_TYPE(obj)->tp_name);
+ } else {
+ sprintf(localbuf, "%s instance", Py_TYPE(obj)->tp_name);
+ }
+ // TODO: detect the size of buf and make sure that size(buf) >= size(localbuf).
+ strcpy(buf, localbuf);
+ return 1;
+}
+
+extern npy_intp
+f2py_size_impl(PyArrayObject* var, ...)
+{
+ npy_intp sz = 0;
+ npy_intp dim;
+ npy_intp rank;
+ va_list argp;
+ va_start(argp, var);
+ dim = va_arg(argp, npy_int);
+ if (dim==-1)
+ {
+ sz = PyArray_SIZE(var);
+ }
+ else
+ {
+ rank = PyArray_NDIM(var);
+ if (dim>=1 && dim<=rank)
+ sz = PyArray_DIM(var, dim-1);
+ else
+ fprintf(stderr, "f2py_size: 2nd argument value=%" NPY_INTP_FMT
+ " fails to satisfy 1<=value<=%" NPY_INTP_FMT
+ ". Result will be 0.\n", dim, rank);
+ }
+ va_end(argp);
+ return sz;
+}
+
/*********************************************/
/* Compatibility functions for Python >= 3.0 */
/*********************************************/
diff --git a/numpy/f2py/src/fortranobject.h b/numpy/f2py/src/fortranobject.h
index a1e9fdbdf..376b83dad 100644
--- a/numpy/f2py/src/fortranobject.h
+++ b/numpy/f2py/src/fortranobject.h
@@ -6,6 +6,7 @@ extern "C" {
#include <Python.h>
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#ifdef FORTRANOBJECT_C
#define NO_IMPORT_ARRAY
#endif
@@ -44,6 +45,7 @@ Author: Pearu Peterson <pearu@cens.ioc.ee>
*/
#define F2PY_MAX_DIMS 40
+#define F2PY_MESSAGE_BUFFER_SIZE 300 // Increase on "stack smashing detected"
typedef void (*f2py_set_data_func)(char *, npy_intp *);
typedef void (*f2py_void_func)(void);
@@ -61,6 +63,7 @@ typedef struct {
npy_intp d[F2PY_MAX_DIMS];
} dims; /* dimensions of the array, || not used */
int type; /* PyArray_<type> || not used */
+ int elsize; /* Element size || not used */
char *data; /* pointer to array || Fortran routine */
f2py_init_func func; /* initialization function for
allocatable arrays:
@@ -125,6 +128,14 @@ F2PyGetThreadLocalCallbackPtr(char *key);
: (F2PY_ALIGN8(intent) ? 8 : (F2PY_ALIGN16(intent) ? 16 : 1)))
#define F2PY_CHECK_ALIGNMENT(arr, intent) \
ARRAY_ISALIGNED(arr, F2PY_GET_ALIGNMENT(intent))
+#define F2PY_ARRAY_IS_CHARACTER_COMPATIBLE(arr) ((PyArray_DESCR(arr)->type_num == NPY_STRING && PyArray_DESCR(arr)->elsize >= 1) \
+ || PyArray_DESCR(arr)->type_num == NPY_UINT8)
+#define F2PY_IS_UNICODE_ARRAY(arr) (PyArray_DESCR(arr)->type_num == NPY_UNICODE)
+
+extern PyArrayObject *
+ndarray_from_pyobj(const int type_num, const int elsize_, npy_intp *dims,
+ const int rank, const int intent, PyObject *obj,
+ const char *errmess);
extern PyArrayObject *
array_from_pyobj(const int type_num, npy_intp *dims, const int rank,
@@ -137,6 +148,23 @@ extern void
dump_attrs(const PyArrayObject *arr);
#endif
+ extern int f2py_describe(PyObject *obj, char *buf);
+
+ /* Utility CPP macros and functions that can be used in signature file
+ expressions. See signature-file.rst for documentation.
+ */
+
+#define f2py_itemsize(var) (PyArray_DESCR((capi_ ## var ## _as_array))->elsize)
+#define f2py_size(var, ...) f2py_size_impl((PyArrayObject *)(capi_ ## var ## _as_array), ## __VA_ARGS__, -1)
+#define f2py_rank(var) var ## _Rank
+#define f2py_shape(var,dim) var ## _Dims[dim]
+#define f2py_len(var) f2py_shape(var,0)
+#define f2py_fshape(var,dim) f2py_shape(var,rank(var)-dim-1)
+#define f2py_flen(var) f2py_fshape(var,0)
+#define f2py_slen(var) capi_ ## var ## _len
+
+ extern npy_intp f2py_size_impl(PyArrayObject* var, ...);
+
#ifdef __cplusplus
}
#endif
diff --git a/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c b/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c
index c8ae7b9dc..9a8b4a752 100644
--- a/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c
+++ b/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c
@@ -33,6 +33,7 @@ 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;
@@ -41,8 +42,8 @@ static PyObject *f2py_rout_wrap_call(PyObject *capi_self,
PyObject *arr_capi = Py_None;
int i;
- if (!PyArg_ParseTuple(capi_args,"iOiO|:wrap.call",\
- &type_num,&dims_capi,&intent,&arr_capi))
+ 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));
@@ -58,7 +59,7 @@ static PyObject *f2py_rout_wrap_call(PyObject *capi_self,
goto fail;
}
}
- capi_arr_tmp = array_from_pyobj(type_num,dims,rank,intent|F2PY_INTENT_OUT,arr_capi);
+ 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;
diff --git a/numpy/f2py/tests/test_array_from_pyobj.py b/numpy/f2py/tests/test_array_from_pyobj.py
index 5a084bc3e..2b8c8defc 100644
--- a/numpy/f2py/tests/test_array_from_pyobj.py
+++ b/numpy/f2py/tests/test_array_from_pyobj.py
@@ -6,11 +6,18 @@ import pytest
import numpy as np
-from numpy.core.multiarray import typeinfo
+from numpy.testing import assert_, assert_equal
+from numpy.core.multiarray import typeinfo as _typeinfo
from . import util
wrap = None
+# Extend core typeinfo with CHARACTER to test dtype('c')
+_ti = _typeinfo['STRING']
+typeinfo = dict(
+ CHARACTER=type(_ti)(('c', _ti.num, 8, _ti.alignment, _ti.type)),
+ **_typeinfo)
+
def setup_module():
"""
@@ -56,6 +63,7 @@ def flags2names(flags):
"NOTSWAPPED",
"WRITEABLE",
"WRITEBACKIFCOPY",
+ "UPDATEIFCOPY",
"BEHAVED",
"BEHAVED_RO",
"CARRAY",
@@ -116,6 +124,9 @@ _type_names = [
"FLOAT",
"DOUBLE",
"CFLOAT",
+ "STRING1",
+ "STRING5",
+ "CHARACTER",
]
_cast_dict = {"BOOL": ["BOOL"]}
@@ -139,6 +150,10 @@ _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']
+
# 32 bit system malloc typically does not provide the alignment required by
# 16 byte long double types this means the inout intent cannot be satisfied
# and several tests fail as the alignment flag can be randomly true or fals
@@ -184,14 +199,33 @@ class Type:
def _init(self, name):
self.NAME = name.upper()
- info = typeinfo[self.NAME]
- self.type_num = getattr(wrap, "NPY_" + self.NAME)
+
+ if self.NAME == 'CHARACTER':
+ info = typeinfo[self.NAME]
+ self.type_num = getattr(wrap, 'NPY_STRING')
+ self.elsize = 1
+ self.dtype = np.dtype('c')
+ elif self.NAME.startswith('STRING'):
+ info = typeinfo[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 = typeinfo[self.NAME]
+ self.type_num = getattr(wrap, 'NPY_' + self.NAME)
+ self.elsize = info.bits // 8
+ self.dtype = np.dtype(info.type)
+
assert self.type_num == info.num
- self.dtype = np.dtype(info.type)
self.type = info.type
- self.elsize = info.bits / 8
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]]
@@ -226,6 +260,11 @@ class Type:
class Array:
+
+ def __repr__(self):
+ return (f'Array({self.type}, {self.dims}, {self.intent},'
+ f' {self.obj})|arr={self.arr}')
+
def __init__(self, typ, dims, intent, obj):
self.type = typ
self.dims = dims
@@ -234,7 +273,9 @@ class Array:
self.obj = obj
# arr.dtypechar may be different from typ.dtypechar
- self.arr = wrap.call(typ.type_num, dims, intent.flags, obj)
+ self.arr = wrap.call(typ.type_num,
+ typ.elsize,
+ dims, intent.flags, obj)
assert isinstance(self.arr, np.ndarray)
@@ -289,7 +330,9 @@ class Array:
self.arr.tobytes(),
self.pyarr.tobytes(),
)) # strides
- assert self.arr_attr[5][-2:] == self.pyarr_attr[5][-2:] # descr
+ assert self.arr_attr[5][-2:] == self.pyarr_attr[5][-2:], repr((
+ self.arr_attr[5], self.pyarr_attr[5]
+ )) # descr
assert self.arr_attr[6] == self.pyarr_attr[6], repr((
self.arr_attr[6],
self.pyarr_attr[6],
@@ -338,8 +381,6 @@ class TestIntent:
class TestSharedMemory:
- num2seq = [1, 2]
- num23seq = [[1, 2, 3], [4, 5, 6]]
@pytest.fixture(autouse=True, scope="class", params=_type_names)
def setup_type(self, request):
@@ -347,6 +388,21 @@ class TestSharedMemory:
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()
@@ -367,9 +423,9 @@ class TestSharedMemory:
"""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"))
+ obj.setflags(write=(write == 'w'))
a = self.array(obj.shape,
- ((order == "C" and intent.in_.c) or intent.in_), obj)
+ ((order == 'C' and intent.in_.c) or intent.in_), obj)
assert a.has_shared_memory()
def test_inout_2seq(self):
@@ -496,6 +552,9 @@ class TestSharedMemory:
def test_in_cache_from_2casttype_failure(self):
for t in self.type.all_types():
+ if t.NAME == 'STRING':
+ # string elsize is 0, so skipping the test
+ continue
if t.elsize >= self.type.elsize:
continue
obj = np.array(self.num2seq, dtype=t.dtype)
diff --git a/numpy/f2py/tests/test_callback.py b/numpy/f2py/tests/test_callback.py
index 4e91430fd..335b78413 100644
--- a/numpy/f2py/tests/test_callback.py
+++ b/numpy/f2py/tests/test_callback.py
@@ -84,8 +84,8 @@ class TestF77Callback(util.F2PyTest):
r = t(a.mth)
assert r == 9
- @pytest.mark.skipif(sys.platform == "win32",
- reason="Fails with MinGW64 Gfortran (Issue #9673)")
+ @pytest.mark.skipif(sys.platform == 'win32',
+ reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback(self):
def callback(code):
if code == "r":
@@ -97,24 +97,27 @@ class TestF77Callback(util.F2PyTest):
r = f(callback)
assert r == 0
- @pytest.mark.skipif(sys.platform == "win32",
- reason="Fails with MinGW64 Gfortran (Issue #9673)")
+ @pytest.mark.skipif(sys.platform == 'win32',
+ reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback_array(self):
# See gh-10027
- cu = np.zeros((1, 8), "S1")
+ cu1 = np.zeros((1, ), "S8")
+ cu2 = np.zeros((1, 8), "c")
+ cu3 = np.array([""], "S8")
def callback(cu, lencu):
- if cu.shape != (lencu, 8):
+ if cu.shape != (lencu,):
return 1
- if cu.dtype != "S1":
+ if cu.dtype != "S8":
return 2
if not np.all(cu == b""):
return 3
return 0
f = getattr(self.module, "string_callback_array")
- res = f(callback, cu, len(cu))
- assert res == 0
+ for cu in [cu1, cu2, cu3]:
+ res = f(callback, cu, len(cu))
+ assert res == 0
def test_threadsafety(self):
# Segfaults if the callback handling is not threadsafe
@@ -223,6 +226,5 @@ class TestGH18335(util.F2PyTest):
def foo(x):
x[0] += 1
- y = np.array([1, 2, 3], dtype=np.int8)
r = self.module.gh18335(foo)
assert r == 123 + 1
diff --git a/numpy/f2py/tests/test_character.py b/numpy/f2py/tests/test_character.py
new file mode 100644
index 000000000..b54b4d981
--- /dev/null
+++ b/numpy/f2py/tests/test_character.py
@@ -0,0 +1,570 @@
+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
+
+
+class TestCharacterString(util.F2PyTest):
+ # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
+ 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):
+ # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
+ 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):
+ # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
+ 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)
+ 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
+ character*5 name, x
+ common name(12)
+ name(i + 1) = x
+ end subroutine {fprefix}_gh6308
+
+ subroutine {fprefix}_gh4519(x)
+ character(len=*), intent(in) :: x(:)
+ !f2py intent(out) x
+ integer :: i
+ do i=1, size(x)
+ print*, "x(",i,")=", x(i)
+ end do
+ 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
+ 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'
+ 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'
+ end subroutine {fprefix}_character_bc_old
+ """)
+
+ def test_gh18684(self):
+ # Test character(len=5) and character*5 usages
+ 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):
+ # Test character string array in a common block
+ 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):
+ # Test array of assumed length strings
+ 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):
+ # Test returning a copy of assumed length string
+ f = getattr(self.module, self.fprefix + '_gh3425')
+ # f is equivalent to bytes.upper
+
+ 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'))
diff --git a/numpy/f2py/tests/test_crackfortran.py b/numpy/f2py/tests/test_crackfortran.py
index ea618bf33..53edfc091 100644
--- a/numpy/f2py/tests/test_crackfortran.py
+++ b/numpy/f2py/tests/test_crackfortran.py
@@ -1,3 +1,4 @@
+import codecs
import pytest
import numpy as np
from numpy.f2py.crackfortran import markinnerspaces
@@ -18,7 +19,7 @@ class TestNoSpace(util.F2PyTest):
assert np.allclose(k, w + 1)
self.module.subc([w, k])
assert np.allclose(k, w + 1)
- assert self.module.t0(23) == b"2"
+ assert self.module.t0("23") == b"2"
class TestPublicPrivate:
@@ -122,7 +123,6 @@ class TestMarkinnerspaces:
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.
@@ -231,3 +231,30 @@ class TestModuleDeclaration:
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):
+ # gh-635
+ f_path = tmp_path / f"input_with_{encoding}_encoding.f90"
+ # explicit BOM is required for UTF8
+ bom = {'utf-8': codecs.BOM_UTF8}.get(encoding, b'')
+ with f_path.open('w', encoding=encoding) as ff:
+ ff.write(bom.decode(encoding) +
+ """
+ subroutine foo()
+ end subroutine foo
+ """)
+ mod = crackfortran.crackfortran([str(f_path)])
+ assert mod[0]['name'] == 'foo'
diff --git a/numpy/f2py/tests/test_docs.py b/numpy/f2py/tests/test_docs.py
new file mode 100644
index 000000000..4aa5f5f5c
--- /dev/null
+++ b/numpy/f2py/tests/test_docs.py
@@ -0,0 +1,55 @@
+import os
+import pytest
+import numpy as np
+from numpy.testing import assert_array_equal, assert_equal
+from . import util
+
+
+def get_docdir():
+ # assuming that documentation tests are run from a source
+ # directory
+ return os.path.abspath(os.path.join(
+ os.path.dirname(__file__),
+ '..', '..', '..',
+ 'doc', 'source', 'f2py'))
+
+
+pytestmark = pytest.mark.skipif(
+ not os.path.isdir(get_docdir()),
+ reason=('Could not find f2py documentation sources'
+ f' ({get_docdir()} does not exists)'))
+
+
+def _path(*a):
+ return os.path.join(*((get_docdir(),) + a))
+
+
+class TestDocAdvanced(util.F2PyTest):
+ # options = ['--debug-capi', '--build-dir', '/tmp/build-f2py']
+ 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(24), 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))
+
+ # TODO: implement test methods for other example Fortran codes
diff --git a/numpy/f2py/tests/test_return_character.py b/numpy/f2py/tests/test_return_character.py
index 21055faef..36c1f10f4 100644
--- a/numpy/f2py/tests/test_return_character.py
+++ b/numpy/f2py/tests/test_return_character.py
@@ -10,7 +10,7 @@ IS_S390X = platform.machine() == "s390x"
class TestReturnCharacter(util.F2PyTest):
def check_function(self, t, tname):
if tname in ["t0", "t1", "s0", "s1"]:
- assert t(23) == b"2"
+ assert t("23") == b"2"
r = t("ab")
assert r == b"a"
r = t(array("ab"))
diff --git a/numpy/f2py/tests/util.py b/numpy/f2py/tests/util.py
index ae81bbfc4..5f815f9d7 100644
--- a/numpy/f2py/tests/util.py
+++ b/numpy/f2py/tests/util.py
@@ -58,7 +58,7 @@ def get_module_dir():
def get_temp_module_name():
# Assume single-threaded, and the module dir usable only by this thread
global _module_num
- d = get_module_dir()
+ get_module_dir()
name = "_test_ext_module_%d" % _module_num
_module_num += 1
if name in sys.modules:
@@ -117,6 +117,8 @@ def build_module(source_files, options=[], skip=[], only=[], module_name=None):
if ext in (".f90", ".f", ".c", ".pyf"):
f2py_sources.append(dst)
+ assert f2py_sources
+
# Prepare options
if module_name is None:
module_name = get_temp_module_name()
@@ -332,7 +334,11 @@ class F2PyTest:
only = []
suffix = ".f"
module = None
- module_name = None
+
+ @property
+ def module_name(self):
+ cls = type(self)
+ return f'_{cls.__module__.rsplit(".",1)[-1]}_{cls.__name__}_ext_module'
def setup(self):
if sys.platform == "win32":