diff options
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/setup.py | 6 | ||||
-rw-r--r-- | numpy/core/src/common/npy_argparse.c | 421 | ||||
-rw-r--r-- | numpy/core/src/common/npy_argparse.h | 96 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_multiarray_tests.c.src | 23 | ||||
-rw-r--r-- | numpy/core/src/multiarray/methods.c | 254 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 167 | ||||
-rw-r--r-- | numpy/core/tests/test_argparse.py | 62 |
7 files changed, 845 insertions, 184 deletions
diff --git a/numpy/core/setup.py b/numpy/core/setup.py index 822f9f580..8c34a3286 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -716,8 +716,10 @@ def configuration(parent_package='',top_path=None): config.add_extension('_multiarray_tests', sources=[join('src', 'multiarray', '_multiarray_tests.c.src'), - join('src', 'common', 'mem_overlap.c')], + join('src', 'common', 'mem_overlap.c'), + join('src', 'common', 'npy_argparse.c')], depends=[join('src', 'common', 'mem_overlap.h'), + join('src', 'common', 'npy_argparse.h'), join('src', 'common', 'npy_extint128.h')], libraries=['npymath']) @@ -731,6 +733,7 @@ def configuration(parent_package='',top_path=None): join('src', 'common', 'cblasfuncs.h'), join('src', 'common', 'lowlevel_strided_loops.h'), join('src', 'common', 'mem_overlap.h'), + join('src', 'common', 'npy_argparse.h'), join('src', 'common', 'npy_cblas.h'), join('src', 'common', 'npy_config.h'), join('src', 'common', 'npy_ctypes.h'), @@ -749,6 +752,7 @@ def configuration(parent_package='',top_path=None): common_src = [ join('src', 'common', 'array_assign.c'), join('src', 'common', 'mem_overlap.c'), + join('src', 'common', 'npy_argparse.c'), join('src', 'common', 'npy_longdouble.c'), join('src', 'common', 'templ_common.h.src'), join('src', 'common', 'ucsnarrow.c'), diff --git a/numpy/core/src/common/npy_argparse.c b/numpy/core/src/common/npy_argparse.c new file mode 100644 index 000000000..3df780990 --- /dev/null +++ b/numpy/core/src/common/npy_argparse.c @@ -0,0 +1,421 @@ +#include "Python.h" + +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _MULTIARRAYMODULE + +#include "numpy/ndarraytypes.h" +#include "npy_argparse.h" +#include "npy_pycompat.h" +#include "npy_import.h" + +#include "arrayfunction_override.h" + + +/** + * Small wrapper converting to array just like CPython does. + * + * We could use our own PyArray_PyIntAsInt function, but it handles floats + * differently. + * A disadvantage of this function compared to ``PyArg_*("i")`` code is that + * it will not say which parameter is wrong. + * + * @param obj The python object to convert + * @param value The output value + * + * @returns 0 on failure and 1 on success (`NPY_FAIL`, `NPY_SUCCEED`) + */ +NPY_NO_EXPORT int +PyArray_PythonPyIntFromInt(PyObject *obj, int *value) +{ + /* Pythons behaviour is to check only for float explicitly... */ + if (NPY_UNLIKELY(PyFloat_Check(obj))) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float"); + return NPY_FAIL; + } + + long result = PyLong_AsLong(obj); + if (NPY_UNLIKELY((result == -1) && PyErr_Occurred())) { + return NPY_FAIL; + } + if (NPY_UNLIKELY((result > INT_MAX) || (result < INT_MIN))) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C int"); + return NPY_FAIL; + } + else { + *value = (int)result; + return NPY_SUCCEED; + } +} + + +typedef int convert(PyObject *, void *); + +/** + * Internal function to initialize keyword argument parsing. + * + * This does a few simple jobs: + * + * * Check the input for consistency to find coding errors, for example + * a parameter not marked with | after one marked with | (optional). + * 2. Find the number of positional-only arguments, the number of + * total, required, and keyword arguments. + * 3. Intern all keyword arguments strings to allow fast, identity based + * parsing and avoid string creation overhead on each call. + * + * @param funcname Name of the function, mainly used for errors. + * @param cache A cache object stored statically in the parsing function + * @param va_orig Argument list to npy_parse_arguments + * @return 0 on success, -1 on failure + */ +static int +initialize_keywords(const char *funcname, + _NpyArgParserCache *cache, va_list va_orig) { + va_list va; + int nargs = 0; + int nkwargs = 0; + int npositional_only = 0; + int nrequired = 0; + int npositional = 0; + char state = '\0'; + + va_copy(va, va_orig); + while (1) { + /* Count length first: */ + char *name = va_arg(va, char *); + convert *converter = va_arg(va, convert *); + void *data = va_arg(va, void *); + + /* Check if this is the sentinel, only converter may be NULL */ + if ((name == NULL) && (converter == NULL) && (data == NULL)) { + break; + } + + if (name == NULL) { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: name is NULL in %s() at " + "argument %d.", funcname, nargs); + va_end(va); + return -1; + } + if (data == NULL) { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: data is NULL in %s() at " + "argument %d.", funcname, nargs); + va_end(va); + return -1; + } + + nargs += 1; + if (*name == '|') { + if (state == '$') { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: positional argument `|` " + "after keyword only `$` one to %s() at argument %d.", + funcname, nargs); + va_end(va); + return -1; + } + state = '|'; + name++; /* advance to actual name. */ + npositional += 1; + } + else if (*name == '$') { + state = '$'; + name++; /* advance to actual name. */ + } + else { + if (state != '\0') { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: non-required argument after " + "required | or $ one to %s() at argument %d.", + funcname, nargs); + va_end(va); + return -1; + } + + nrequired += 1; + npositional += 1; + } + + if (*name == '\0') { + /* Empty string signals positional only */ + if (state != '\0') { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: non-kwarg marked with | or $ " + "to %s() at argument %d.", funcname, nargs); + va_end(va); + return -1; + } + npositional_only += 1; + } + else { + nkwargs += 1; + } + } + va_end(va); + + if (npositional == -1) { + npositional = nargs; + } + + if (nargs > _NPY_MAX_KWARGS) { + PyErr_Format(PyExc_SystemError, + "NumPy internal error: function %s() has %d arguments, but " + "the maximum is currently limited to %d for easier parsing; " + "it can be increased by modifying `_NPY_MAX_KWARGS`.", + funcname, nargs, _NPY_MAX_KWARGS); + return -1; + } + + /* + * Do any necessary string allocation and interning, + * creating a caching object. + */ + cache->nargs = nargs; + cache->npositional_only = npositional_only; + cache->npositional = npositional; + cache->nrequired = nrequired; + + /* NULL kw_strings for easier cleanup (and NULL termination) */ + memset(cache->kw_strings, 0, sizeof(PyObject *) * (nkwargs + 1)); + + va_copy(va, va_orig); + for (int i = 0; i < nargs; i++) { + /* Advance through non-kwargs, which do not require setup. */ + char *name = va_arg(va, char *); + va_arg(va, convert *); + va_arg(va, void *); + + if (*name == '|' || *name == '$') { + name++; /* ignore | and $ */ + } + if (i >= npositional_only) { + int i_kwarg = i - npositional_only; + cache->kw_strings[i_kwarg] = PyUString_InternFromString(name); + if (cache->kw_strings[i_kwarg] == NULL) { + va_end(va); + goto error; + } + } + } + + va_end(va); + return 0; + +error: + for (int i = 0; i < nkwargs; i++) { + Py_XDECREF(cache->kw_strings[i]); + } + cache->npositional = -1; /* not initialized */ + return -1; +} + + +static int +raise_incorrect_number_of_positional_args(const char *funcname, + const _NpyArgParserCache *cache, Py_ssize_t len_args) +{ + if (cache->npositional == cache->nrequired) { + PyErr_Format(PyExc_TypeError, + "%s() takes %d positional arguments but %zd were given", + funcname, cache->npositional, len_args); + } + else { + PyErr_Format(PyExc_TypeError, + "%s() takes from %d to %d positional arguments but " + "%zd were given", + funcname, cache->nrequired, cache->npositional, len_args); + } + return -1; +} + +static void +raise_missing_argument(const char *funcname, + const _NpyArgParserCache *cache, int i) +{ + if (i < cache->npositional_only) { + PyErr_Format(PyExc_TypeError, + "%s() missing required positional argument %d", + funcname, i); + } + else { + PyObject *kw = cache->kw_strings[i - cache->npositional_only]; + PyErr_Format(PyExc_TypeError, + "%s() missing required argument '%S' (pos %d)", + funcname, kw, i); + } +} + + +/** + * Generic helper for argument parsing + * + * See macro version for an example pattern of how to use this function. + * + * @param funcname + * @param cache + * @param args Python passed args (METH_FASTCALL) + * @param len_args + * @param kwnames + * @param ... List of arguments (see macro version). + * + * @return Returns 0 on success and -1 on failure. + */ +NPY_NO_EXPORT int +_npy_parse_arguments(const char *funcname, + /* cache_ptr is a NULL initialized persistent storage for data */ + _NpyArgParserCache *cache, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames, + /* ... is NULL, NULL, NULL terminated: name, converter, value */ + ...) +{ + if (NPY_UNLIKELY(cache->npositional == -1)) { + va_list va; + va_start(va, kwnames); + + int res = initialize_keywords(funcname, cache, va); + va_end(va); + if (res < 0) { + return -1; + } + } + + if (NPY_UNLIKELY(len_args > cache->npositional)) { + return raise_incorrect_number_of_positional_args( + funcname, cache, len_args); + } + + /* NOTE: Could remove the limit but too many kwargs are slow anyway. */ + PyObject *all_arguments[NPY_MAXARGS]; + + for (Py_ssize_t i = 0; i < len_args; i++) { + all_arguments[i] = args[i]; + } + + /* Without kwargs, do not iterate all converters. */ + int max_nargs = (int)len_args; + Py_ssize_t len_kwargs = 0; + + /* If there are any kwargs, first handle them */ + if (NPY_LIKELY(kwnames != NULL)) { + len_kwargs = PyTuple_GET_SIZE(kwnames); + max_nargs = cache->nargs; + + for (int i = len_args; i < cache->nargs; i++) { + all_arguments[i] = NULL; + } + + for (Py_ssize_t i = 0; i < len_kwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = args[i + len_args]; + PyObject *const *name; + + /* Super-fast path, check identity: */ + for (name = cache->kw_strings; *name != NULL; name++) { + if (*name == key) { + break; + } + } + if (NPY_UNLIKELY(*name == NULL)) { + /* Slow fallback, if identity checks failed for some reason */ + for (name = cache->kw_strings; *name != NULL; name++) { + int eq = PyObject_RichCompareBool(*name, key, Py_EQ); + if (eq == -1) { + return -1; + } + else if (eq) { + break; + } + } + if (NPY_UNLIKELY(*name == NULL)) { + /* Invalid keyword argument. */ + PyErr_Format(PyExc_TypeError, + "%s() got an unexpected keyword argument '%S'", + funcname, key); + return -1; + } + } + + ssize_t param_pos = ( + (name - cache->kw_strings) + cache->npositional_only); + + /* There could be an identical positional argument */ + if (NPY_UNLIKELY(all_arguments[param_pos] != NULL)) { + PyErr_Format(PyExc_TypeError, + "argument for %s() given by name ('%S') and position " + "(position %zd)", funcname, key, param_pos); + return -1; + } + + all_arguments[param_pos] = value; + } + } + + /* + * There cannot be too many args, too many kwargs would find an + * incorrect one above. + */ + assert(len_args + len_kwargs <= cache->nargs); + + /* At this time `all_arguments` holds either NULLs or the objects */ + va_list va; + va_start(va, kwnames); + + for (int i = 0; i < max_nargs; i++) { + va_arg(va, char *); + convert *converter = va_arg(va, convert *); + void *data = va_arg(va, void *); + + if (all_arguments[i] == NULL) { + continue; + } + + int res; + if (converter == NULL) { + *((PyObject **) data) = all_arguments[i]; + continue; + } + res = converter(all_arguments[i], data); + + if (NPY_UNLIKELY(res == NPY_SUCCEED)) { + continue; + } + else if (NPY_UNLIKELY(res == NPY_FAIL)) { + /* It is usually the users responsibility to clean up. */ + goto converting_failed; + } + else if (NPY_UNLIKELY(res == Py_CLEANUP_SUPPORTED)) { + /* TODO: Implementing cleanup if/when needed should not be hard */ + PyErr_Format(PyExc_SystemError, + "converter cleanup of parameter %d to %s() not supported.", + i, funcname); + goto converting_failed; + } + assert(0); + } + + /* Required arguments are typically not passed as keyword arguments */ + if (NPY_UNLIKELY(len_args < cache->nrequired)) { + /* (PyArg_* also does this after the actual parsing is finished) */ + if (NPY_UNLIKELY(max_nargs < cache->nrequired)) { + raise_missing_argument(funcname, cache, max_nargs); + goto converting_failed; + } + for (int i = 0; i < cache->nrequired; i++) { + if (NPY_UNLIKELY(all_arguments[i] == NULL)) { + raise_missing_argument(funcname, cache, i); + goto converting_failed; + } + } + } + + va_end(va); + return 0; + +converting_failed: + va_end(va); + return -1; + +} diff --git a/numpy/core/src/common/npy_argparse.h b/numpy/core/src/common/npy_argparse.h new file mode 100644 index 000000000..5da535c91 --- /dev/null +++ b/numpy/core/src/common/npy_argparse.h @@ -0,0 +1,96 @@ +#ifndef _NPY_ARGPARSE_H +#define _NPY_ARGPARSE_H + +#include "Python.h" +#include "numpy/ndarraytypes.h" + +/* + * This file defines macros to help with keyword argument parsing. + * This solves two issues as of now: + * 1. Pythons C-API PyArg_* keyword argument parsers are slow, due to + * not caching the strings they use. + * 2. It allows the use of METH_ARGPARSE (and `tp_vectorcall`) + * when available in Python, which removes a large chunk of overhead. + * + * Internally CPython achieves similar things by using a code generator + * argument clinic. NumPy may well decide to use argument clinic or a different + * solution in the future. + */ + +NPY_NO_EXPORT int +PyArray_PythonPyIntFromInt(PyObject *obj, int *value); + + +#define _NPY_MAX_KWARGS 15 + +typedef struct { + int npositional; + int nargs; + int npositional_only; + int nrequired; + /* Null terminated list of keyword argument name strings */ + PyObject *kw_strings[_NPY_MAX_KWARGS+1]; +} _NpyArgParserCache; + + +/* + * The sole purpose of this macro is to hide the argument parsing cache. + * Since this cache must be static, this also removes a source of error. + */ +#define NPY_PREPARE_ARGPARSER static _NpyArgParserCache __argparse_cache = {-1} + +/** + * Macro to help with argument parsing. + * + * The pattern for using this macro is by defining the method as: + * + * @code + * static PyObject * + * my_method(PyObject *self, + * PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) + * { + * NPY_PREPARE_ARGPARSER; + * + * PyObject *argument1, *argument3; + * int argument2 = -1; + * if (npy_parse_arguments("method", args, len_args, kwnames), + * "argument1", NULL, &argument1, + * "|argument2", &PyArray_PythonPyIntFromInt, &argument2, + * "$argument3", NULL, &argument3, + * NULL, NULL, NULL) < 0) { + * return NULL; + * } + * } + * @endcode + * + * The `NPY_PREPARE_ARGPARSER` macro sets up a static cache variable necessary + * to hold data for speeding up the parsing. `npy_parse_arguments` must be + * used in cunjunction with the macro defined in the same scope. + * (No two `npy_parse_arguments` may share a single `NPY_PREPARE_ARGPARSER`.) + * + * @param funcname + * @param args Python passed args (METH_FASTCALL) + * @param len_args Number of arguments (not flagged) + * @param kwnames Tuple as passed by METH_FASTCALL or NULL. + * @param ... List of arguments must be param1_name, param1_converter, + * *param1_outvalue, param2_name, ..., NULL, NULL, NULL. + * Where name is ``char *``, ``converter`` a python converter + * function or NULL and ``outvalue`` is the ``void *`` passed to + * the converter (holding the converted data or a borrowed + * reference if converter is NULL). + * + * @return Returns 0 on success and -1 on failure. + */ +NPY_NO_EXPORT int +_npy_parse_arguments(const char *funcname, + /* cache_ptr is a NULL initialized persistent storage for data */ + _NpyArgParserCache *cache_ptr, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames, + /* va_list is NULL, NULL, NULL terminated: name, converter, value */ + ...) NPY_GCC_NONNULL(1); + +#define npy_parse_arguments(funcname, args, len_args, kwnames, ...) \ + _npy_parse_arguments(funcname, &__argparse_cache, \ + args, len_args, kwnames, __VA_ARGS__) + +#endif /* _NPY_ARGPARSE_H */ diff --git a/numpy/core/src/multiarray/_multiarray_tests.c.src b/numpy/core/src/multiarray/_multiarray_tests.c.src index 3c8caefce..febcc8512 100644 --- a/numpy/core/src/multiarray/_multiarray_tests.c.src +++ b/numpy/core/src/multiarray/_multiarray_tests.c.src @@ -7,6 +7,7 @@ #include "numpy/npy_math.h" #include "numpy/halffloat.h" #include "common.h" +#include "npy_argparse.h" #include "mem_overlap.h" #include "npy_extint128.h" #include "array_method.h" @@ -19,6 +20,25 @@ #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +static PyObject * +argparse_example_function(PyObject *NPY_UNUSED(mod), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) +{ + NPY_PREPARE_ARGPARSER; + int arg1; + PyObject *arg2, *arg3, *arg4; + if (npy_parse_arguments("func", args, len_args, kwnames, + "", &PyArray_PythonPyIntFromInt, &arg1, + "arg2", NULL, &arg2, + "|arg3", NULL, &arg3, + "$arg3", NULL, &arg4, + NULL, NULL, NULL) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + /* test PyArray_IsPythonScalar, before including private py3 compat header */ static PyObject * IsPythonScalar(PyObject * dummy, PyObject *args) @@ -2255,6 +2275,9 @@ run_intp_converter(PyObject* NPY_UNUSED(self), PyObject *args) } static PyMethodDef Multiarray_TestsMethods[] = { + {"argparse_example_function", + (PyCFunction)argparse_example_function, + METH_KEYWORDS | METH_FASTCALL, NULL}, {"IsPythonScalar", IsPythonScalar, METH_VARARGS, NULL}, diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 04ce53ed7..ff5a5d8bc 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -10,6 +10,7 @@ #include "numpy/arrayscalars.h" #include "arrayfunction_override.h" +#include "npy_argparse.h" #include "npy_config.h" #include "npy_pycompat.h" #include "npy_import.h" @@ -103,20 +104,23 @@ forward_ndarray_method(PyArrayObject *self, PyObject *args, PyObject *kwds, static PyObject * -array_take(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_take(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int dimension = NPY_MAXDIMS; PyObject *indices; PyArrayObject *out = NULL; NPY_CLIPMODE mode = NPY_RAISE; - static char *kwlist[] = {"indices", "axis", "out", "mode", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&O&O&:take", kwlist, - &indices, - PyArray_AxisConverter, &dimension, - PyArray_OutputConverter, &out, - PyArray_ClipmodeConverter, &mode)) + if (npy_parse_arguments("take", args, len_args, kwnames, + "indices", NULL, &indices, + "|axis", &PyArray_AxisConverter, &dimension, + "|out", &PyArray_OutputConverter, &out, + "|mode", &PyArray_ClipmodeConverter, &mode, + NULL, NULL, NULL) < 0) { return NULL; + } PyObject *ret = PyArray_TakeFrom(self, indices, dimension, out, mode); @@ -199,14 +203,16 @@ array_reshape(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_squeeze(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_squeeze(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *axis_in = NULL; npy_bool axis_flags[NPY_MAXDIMS]; + NPY_PREPARE_ARGPARSER; - static char *kwlist[] = {"axis", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:squeeze", kwlist, - &axis_in)) { + if (npy_parse_arguments("squeeze", args, len_args, kwnames, + "|axis", NULL, &axis_in, + NULL, NULL, NULL) < 0) { return NULL; } @@ -224,16 +230,18 @@ array_squeeze(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_view(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_view(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *out_dtype = NULL; PyObject *out_type = NULL; PyArray_Descr *dtype = NULL; + NPY_PREPARE_ARGPARSER; - static char *kwlist[] = {"dtype", "type", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO:view", kwlist, - &out_dtype, - &out_type)) { + if (npy_parse_arguments("view", args, len_args, kwnames, + "|dtype", NULL, &out_dtype, + "|type", NULL, &out_type, + NULL, NULL, NULL) < 0) { return NULL; } @@ -271,16 +279,19 @@ array_view(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_argmax(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_argmax(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis = NPY_MAXDIMS; PyArrayObject *out = NULL; - static char *kwlist[] = {"axis", "out", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&:argmax", kwlist, - PyArray_AxisConverter, &axis, - PyArray_OutputConverter, &out)) + if (npy_parse_arguments("argmax", args, len_args, kwnames, + "|axis", &PyArray_AxisConverter, &axis, + "|out", &PyArray_OutputConverter, &out, + NULL, NULL, NULL) < 0) { return NULL; + } PyObject *ret = PyArray_ArgMax(self, axis, out); @@ -294,16 +305,19 @@ array_argmax(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_argmin(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_argmin(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis = NPY_MAXDIMS; PyArrayObject *out = NULL; - static char *kwlist[] = {"axis", "out", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&:argmin", kwlist, - PyArray_AxisConverter, &axis, - PyArray_OutputConverter, &out)) + if (npy_parse_arguments("argmin", args, len_args, kwnames, + "|axis", &PyArray_AxisConverter, &axis, + "|out", &PyArray_OutputConverter, &out, + NULL, NULL, NULL) < 0) { return NULL; + } PyObject *ret = PyArray_ArgMin(self, axis, out); @@ -804,10 +818,9 @@ array_setscalar(PyArrayObject *self, PyObject *args) static PyObject * -array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_astype(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { - static char *kwlist[] = {"dtype", "order", "casting", - "subok", "copy", NULL}; PyArray_Descr *dtype = NULL; /* * TODO: UNSAFE default for compatibility, I think @@ -816,13 +829,15 @@ array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds) NPY_CASTING casting = NPY_UNSAFE_CASTING; NPY_ORDER order = NPY_KEEPORDER; int forcecopy = 1, subok = 1; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&ii:astype", kwlist, - PyArray_DescrConverter, &dtype, - PyArray_OrderConverter, &order, - PyArray_CastingConverter, &casting, - &subok, - &forcecopy)) { + NPY_PREPARE_ARGPARSER; + + if (npy_parse_arguments("astype", args, len_args, kwnames, + "dtype", &PyArray_DescrConverter, &dtype, + "|order", &PyArray_OrderConverter, &order, + "|casting", &PyArray_CastingConverter, &casting, + "|subok", &PyArray_PythonPyIntFromInt, &subok, + "|copy", &PyArray_PythonPyIntFromInt, &forcecopy, + NULL, NULL, NULL) < 0) { Py_XDECREF(dtype); return NULL; } @@ -1143,13 +1158,15 @@ array_function(PyArrayObject *NPY_UNUSED(self), PyObject *c_args, PyObject *c_kw } static PyObject * -array_copy(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_copy(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { NPY_ORDER order = NPY_CORDER; - static char *kwlist[] = {"order", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:copy", kwlist, - PyArray_OrderConverter, &order)) { + if (npy_parse_arguments("copy", args, len_args, kwnames, + "|order", PyArray_OrderConverter, &order, + NULL, NULL, NULL) < 0) { return NULL; } @@ -1257,7 +1274,8 @@ array_choose(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_sort(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_sort(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis=-1; int val; @@ -1265,12 +1283,13 @@ array_sort(PyArrayObject *self, PyObject *args, PyObject *kwds) PyObject *order = NULL; PyArray_Descr *saved = NULL; PyArray_Descr *newd; - static char *kwlist[] = {"axis", "kind", "order", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iO&O:sort", kwlist, - &axis, - PyArray_SortkindConverter, &sortkind, - &order)) { + if (npy_parse_arguments("sort", args, len_args, kwnames, + "|axis", &PyArray_PythonPyIntFromInt, &axis, + "|kind", &PyArray_SortkindConverter, &sortkind, + "|order", NULL, &order, + NULL, NULL, NULL) < 0) { return NULL; } if (order == Py_None) { @@ -1313,7 +1332,8 @@ array_sort(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_partition(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_partition(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis=-1; int val; @@ -1321,16 +1341,16 @@ array_partition(PyArrayObject *self, PyObject *args, PyObject *kwds) PyObject *order = NULL; PyArray_Descr *saved = NULL; PyArray_Descr *newd; - static char *kwlist[] = {"kth", "axis", "kind", "order", NULL}; PyArrayObject * ktharray; PyObject * kthobj; + NPY_PREPARE_ARGPARSER; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iO&O:partition", kwlist, - &kthobj, - &axis, - PyArray_SelectkindConverter, &sortkind, - &order)) { + if (npy_parse_arguments("partition", args, len_args, kwnames, + "kth", NULL, &kthobj, + "|axis", &PyArray_PythonPyIntFromInt, &axis, + "|kind", &PyArray_SelectkindConverter, &sortkind, + "|order", NULL, &order, + NULL, NULL, NULL) < 0) { return NULL; } @@ -1381,18 +1401,20 @@ array_partition(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_argsort(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_argsort(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis = -1; NPY_SORTKIND sortkind = NPY_QUICKSORT; PyObject *order = NULL, *res; PyArray_Descr *newd, *saved=NULL; - static char *kwlist[] = {"axis", "kind", "order", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&O:argsort", kwlist, - PyArray_AxisConverter, &axis, - PyArray_SortkindConverter, &sortkind, - &order)) { + if (npy_parse_arguments("argsort", args, len_args, kwnames, + "|axis", &PyArray_AxisConverter, &axis, + "|kind", &PyArray_SortkindConverter, &sortkind, + "|order", NULL, &order, + NULL, NULL, NULL) < 0) { return NULL; } if (order == Py_None) { @@ -1433,21 +1455,23 @@ array_argsort(PyArrayObject *self, PyObject *args, PyObject *kwds) static PyObject * -array_argpartition(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_argpartition(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis = -1; NPY_SELECTKIND sortkind = NPY_INTROSELECT; PyObject *order = NULL, *res; PyArray_Descr *newd, *saved=NULL; - static char *kwlist[] = {"kth", "axis", "kind", "order", NULL}; PyObject * kthobj; PyArrayObject * ktharray; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&O&O:argpartition", kwlist, - &kthobj, - PyArray_AxisConverter, &axis, - PyArray_SelectkindConverter, &sortkind, - &order)) { + if (npy_parse_arguments("argpartition", args, len_args, kwnames, + "kth", NULL, &kthobj, + "|axis", &PyArray_AxisConverter, &axis, + "|kind", &PyArray_SelectkindConverter, &sortkind, + "|order", NULL, &order, + NULL, NULL, NULL) < 0) { return NULL; } if (order == Py_None) { @@ -1494,17 +1518,20 @@ array_argpartition(PyArrayObject *self, PyObject *args, PyObject *kwds) } static PyObject * -array_searchsorted(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_searchsorted(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { - static char *kwlist[] = {"v", "side", "sorter", NULL}; PyObject *keys; PyObject *sorter; NPY_SEARCHSIDE side = NPY_SEARCHLEFT; + NPY_PREPARE_ARGPARSER; sorter = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&O:searchsorted", - kwlist, &keys, - PyArray_SearchsideConverter, &side, &sorter)) { + if (npy_parse_arguments("searchsorted", args, len_args, kwnames, + "v", NULL, &keys, + "|side", &PyArray_SearchsideConverter, &side, + "|sorter", NULL, &sorter, + NULL, NULL, NULL) < 0) { return NULL; } if (sorter == Py_None) { @@ -2285,14 +2312,17 @@ array_cumprod(PyArrayObject *self, PyObject *args, PyObject *kwds) static PyObject * -array_dot(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_dot(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *a = (PyObject *)self, *b, *o = NULL; PyArrayObject *ret; - static char* kwlist[] = {"b", "out", NULL}; + NPY_PREPARE_ARGPARSER; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:dot", kwlist, &b, &o)) { + if (npy_parse_arguments("dot", args, len_args, kwnames, + "b", NULL, &b, + "|out", NULL, &o, + NULL, NULL, NULL) < 0) { return NULL; } @@ -2374,20 +2404,22 @@ array_nonzero(PyArrayObject *self, PyObject *args) static PyObject * -array_trace(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_trace(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { int axis1 = 0, axis2 = 1, offset = 0; PyArray_Descr *dtype = NULL; PyArrayObject *out = NULL; int rtype; - static char *kwlist[] = {"offset", "axis1", "axis2", "dtype", "out", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiO&O&:trace", kwlist, - &offset, - &axis1, - &axis2, - PyArray_DescrConverter2, &dtype, - PyArray_OutputConverter, &out)) { + NPY_PREPARE_ARGPARSER; + + if (npy_parse_arguments("trace", args, len_args, kwnames, + "|offset", &PyArray_PythonPyIntFromInt, &offset, + "|axis1", &PyArray_PythonPyIntFromInt, &axis1, + "|axis2", &PyArray_PythonPyIntFromInt, &axis2, + "|dtype", &PyArray_DescrConverter2, &dtype, + "|out", &PyArray_OutputConverter, &out, + NULL, NULL, NULL) < 0) { Py_XDECREF(dtype); return NULL; } @@ -2448,13 +2480,15 @@ array_diagonal(PyArrayObject *self, PyObject *args, PyObject *kwds) static PyObject * -array_flatten(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_flatten(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { NPY_ORDER order = NPY_CORDER; - static char *kwlist[] = {"order", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:flatten", kwlist, - PyArray_OrderConverter, &order)) { + if (npy_parse_arguments("flatten", args, len_args, kwnames, + "|order", PyArray_OrderConverter, &order, + NULL, NULL, NULL) < 0) { return NULL; } return PyArray_Flatten(self, order); @@ -2462,13 +2496,15 @@ array_flatten(PyArrayObject *self, PyObject *args, PyObject *kwds) static PyObject * -array_ravel(PyArrayObject *self, PyObject *args, PyObject *kwds) +array_ravel(PyArrayObject *self, + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { NPY_ORDER order = NPY_CORDER; - static char *kwlist[] = {"order", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:ravel", kwlist, - PyArray_OrderConverter, &order)) { + if (npy_parse_arguments("ravel", args, len_args, kwnames, + "|order", PyArray_OrderConverter, &order, + NULL, NULL, NULL) < 0) { return NULL; } return PyArray_Ravel(self, order); @@ -2724,19 +2760,19 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"argmax", (PyCFunction)array_argmax, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"argmin", (PyCFunction)array_argmin, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"argpartition", (PyCFunction)array_argpartition, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"argsort", (PyCFunction)array_argsort, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"astype", (PyCFunction)array_astype, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"byteswap", (PyCFunction)array_byteswap, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2757,7 +2793,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS, NULL}, {"copy", (PyCFunction)array_copy, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"cumprod", (PyCFunction)array_cumprod, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2769,13 +2805,13 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"dot", (PyCFunction)array_dot, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"fill", (PyCFunction)array_fill, METH_VARARGS, NULL}, {"flatten", (PyCFunction)array_flatten, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"getfield", (PyCFunction)array_getfield, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2802,7 +2838,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS, NULL}, {"partition", (PyCFunction)array_partition, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"prod", (PyCFunction)array_prod, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2814,7 +2850,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"ravel", (PyCFunction)array_ravel, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"repeat", (PyCFunction)array_repeat, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2829,7 +2865,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"searchsorted", (PyCFunction)array_searchsorted, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"setfield", (PyCFunction)array_setfield, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2838,10 +2874,10 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"sort", (PyCFunction)array_sort, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"squeeze", (PyCFunction)array_squeeze, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"std", (PyCFunction)array_stddev, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2853,7 +2889,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS, NULL}, {"take", (PyCFunction)array_take, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"tobytes", (PyCFunction)array_tobytes, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -2868,7 +2904,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"trace", (PyCFunction)array_trace, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"transpose", (PyCFunction)array_transpose, METH_VARARGS, NULL}, @@ -2877,6 +2913,6 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"view", (PyCFunction)array_view, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 7915c75be..a0f7afeb5 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -26,7 +26,7 @@ #include "numpy/arrayscalars.h" #include "numpy/npy_math.h" - +#include "npy_argparse.h" #include "npy_config.h" #include "npy_pycompat.h" #include "npy_import.h" @@ -2829,28 +2829,36 @@ array_fastCopyAndTranspose(PyObject *NPY_UNUSED(dummy), PyObject *args) } static PyObject * -array_correlate(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +array_correlate(PyObject *NPY_UNUSED(dummy), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *shape, *a0; int mode = 0; - static char *kwlist[] = {"a", "v", "mode", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|i:correlate", kwlist, - &a0, &shape, &mode)) { + if (npy_parse_arguments("correlate", args, len_args, kwnames, + "a", NULL, &a0, + "v", NULL, &shape, + "|mode", &PyArray_PythonPyIntFromInt, &mode, + NULL, NULL, NULL) < 0) { return NULL; } return PyArray_Correlate(a0, shape, mode); } static PyObject* -array_correlate2(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +array_correlate2(PyObject *NPY_UNUSED(dummy), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *shape, *a0; int mode = 0; - static char *kwlist[] = {"a", "v", "mode", NULL}; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|i:correlate2", kwlist, - &a0, &shape, &mode)) { + if (npy_parse_arguments("correlate2", args, len_args, kwnames, + "a", NULL, &a0, + "v", NULL, &shape, + "|mode", &PyArray_PythonPyIntFromInt, &mode, + NULL, NULL, NULL) < 0) { return NULL; } return PyArray_Correlate2(a0, shape, mode); @@ -3426,6 +3434,42 @@ array_datetime_data(PyObject *NPY_UNUSED(dummy), PyObject *args) return res; } + +static int +trimmode_converter(PyObject *obj, TrimMode *trim) +{ + if (!PyUnicode_Check(obj) || PyUnicode_GetLength(obj) != 1) { + goto error; + } + const char *trimstr = PyUnicode_AsUTF8AndSize(obj, NULL); + + if (trimstr != NULL) { + if (trimstr[0] == 'k') { + *trim = TrimMode_None; + } + else if (trimstr[0] == '.') { + *trim = TrimMode_Zeros; + } + else if (trimstr[0] == '0') { + *trim = TrimMode_LeaveOneZero; + } + else if (trimstr[0] == '-') { + *trim = TrimMode_DptZeros; + } + else { + goto error; + } + } + return NPY_SUCCEED; + +error: + PyErr_Format(PyExc_TypeError, + "if supplied, trim must be 'k', '.', '0' or '-' found `%100S`", + obj); + return NPY_FAIL; +} + + /* * Prints floating-point scalars using the Dragon4 algorithm, scientific mode. * See docstring of `np.format_float_scientific` for description of arguments. @@ -3433,43 +3477,28 @@ array_datetime_data(PyObject *NPY_UNUSED(dummy), PyObject *args) * precision, which is equivalent to `None`. */ static PyObject * -dragon4_scientific(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +dragon4_scientific(PyObject *NPY_UNUSED(dummy), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *obj; - static char *kwlist[] = {"x", "precision", "unique", "sign", "trim", - "pad_left", "exp_digits", NULL}; int precision=-1, pad_left=-1, exp_digits=-1; - char *trimstr=NULL; DigitMode digit_mode; TrimMode trim = TrimMode_None; int sign=0, unique=1; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiisii:dragon4_scientific", - kwlist, &obj, &precision, &unique, &sign, &trimstr, &pad_left, - &exp_digits)) { + NPY_PREPARE_ARGPARSER; + + if (npy_parse_arguments("dragon4_scientific", args, len_args, kwnames, + "x", NULL , &obj, + "|precision", &PyArray_PythonPyIntFromInt, &precision, + "|unique", &PyArray_PythonPyIntFromInt, &unique, + "|sign", &PyArray_PythonPyIntFromInt, &sign, + "|trim", &trimmode_converter, &trim, + "|pad_left", &PyArray_PythonPyIntFromInt, &pad_left, + "|exp_digits", &PyArray_PythonPyIntFromInt, &exp_digits, + NULL, NULL, NULL) < 0) { return NULL; } - if (trimstr != NULL) { - if (strcmp(trimstr, "k") == 0) { - trim = TrimMode_None; - } - else if (strcmp(trimstr, ".") == 0) { - trim = TrimMode_Zeros; - } - else if (strcmp(trimstr, "0") == 0) { - trim = TrimMode_LeaveOneZero; - } - else if (strcmp(trimstr, "-") == 0) { - trim = TrimMode_DptZeros; - } - else { - PyErr_SetString(PyExc_TypeError, - "if supplied, trim must be 'k', '.', '0' or '-'"); - return NULL; - } - } - digit_mode = unique ? DigitMode_Unique : DigitMode_Exact; if (unique == 0 && precision < 0) { @@ -3489,44 +3518,30 @@ dragon4_scientific(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) * precision, which is equivalent to `None`. */ static PyObject * -dragon4_positional(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +dragon4_positional(PyObject *NPY_UNUSED(dummy), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { PyObject *obj; - static char *kwlist[] = {"x", "precision", "unique", "fractional", - "sign", "trim", "pad_left", "pad_right", NULL}; int precision=-1, pad_left=-1, pad_right=-1; - char *trimstr=NULL; CutoffMode cutoff_mode; DigitMode digit_mode; TrimMode trim = TrimMode_None; int sign=0, unique=1, fractional=0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiiisii:dragon4_positional", - kwlist, &obj, &precision, &unique, &fractional, &sign, &trimstr, - &pad_left, &pad_right)) { + NPY_PREPARE_ARGPARSER; + + if (npy_parse_arguments("dragon4_positional", args, len_args, kwnames, + "x", NULL , &obj, + "|precision", &PyArray_PythonPyIntFromInt, &precision, + "|unique", &PyArray_PythonPyIntFromInt, &unique, + "|fractional", &PyArray_PythonPyIntFromInt, &fractional, + "|sign", &PyArray_PythonPyIntFromInt, &sign, + "|trim", &trimmode_converter, &trim, + "|pad_left", &PyArray_PythonPyIntFromInt, &pad_left, + "|pad_right", &PyArray_PythonPyIntFromInt, &pad_right, + NULL, NULL, NULL) < 0) { return NULL; } - if (trimstr != NULL) { - if (strcmp(trimstr, "k") == 0) { - trim = TrimMode_None; - } - else if (strcmp(trimstr, ".") == 0) { - trim = TrimMode_Zeros; - } - else if (strcmp(trimstr, "0") == 0) { - trim = TrimMode_LeaveOneZero; - } - else if (strcmp(trimstr, "-") == 0) { - trim = TrimMode_DptZeros; - } - else { - PyErr_SetString(PyExc_TypeError, - "if supplied, trim must be 'k', '.', '0' or '-'"); - return NULL; - } - } - digit_mode = unique ? DigitMode_Unique : DigitMode_Exact; cutoff_mode = fractional ? CutoffMode_FractionLength : CutoffMode_TotalLength; @@ -4052,15 +4067,19 @@ array_may_share_memory(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject * } static PyObject * -normalize_axis_index(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwds) +normalize_axis_index(PyObject *NPY_UNUSED(self), + PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames) { - static char *kwlist[] = {"axis", "ndim", "msg_prefix", NULL}; int axis; int ndim; PyObject *msg_prefix = Py_None; + NPY_PREPARE_ARGPARSER; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|O:normalize_axis_index", - kwlist, &axis, &ndim, &msg_prefix)) { + if (npy_parse_arguments("normalize_axis_index", args, len_args, kwnames, + "axis", &PyArray_PythonPyIntFromInt, &axis, + "ndim", &PyArray_PythonPyIntFromInt, &ndim, + "|msg_prefix", NULL, &msg_prefix, + NULL, NULL, NULL) < 0) { return NULL; } if (check_and_adjust_axis_msg(&axis, ndim, msg_prefix) < 0) { @@ -4191,10 +4210,10 @@ static struct PyMethodDef array_module_methods[] = { METH_VARARGS, NULL}, {"correlate", (PyCFunction)array_correlate, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"correlate2", (PyCFunction)array_correlate2, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"frombuffer", (PyCFunction)array_frombuffer, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -4241,10 +4260,10 @@ static struct PyMethodDef array_module_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"dragon4_positional", (PyCFunction)dragon4_positional, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"dragon4_scientific", (PyCFunction)dragon4_scientific, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"compare_chararrays", (PyCFunction)compare_chararrays, METH_VARARGS | METH_KEYWORDS, NULL}, @@ -4277,7 +4296,7 @@ static struct PyMethodDef array_module_methods[] = { {"unpackbits", (PyCFunction)io_unpack, METH_VARARGS | METH_KEYWORDS, NULL}, {"normalize_axis_index", (PyCFunction)normalize_axis_index, - METH_VARARGS | METH_KEYWORDS, NULL}, + METH_FASTCALL | METH_KEYWORDS, NULL}, {"set_legacy_print_mode", (PyCFunction)set_legacy_print_mode, METH_VARARGS, NULL}, {"_discover_array_parameters", (PyCFunction)_discover_array_parameters, diff --git a/numpy/core/tests/test_argparse.py b/numpy/core/tests/test_argparse.py new file mode 100644 index 000000000..63a01dee4 --- /dev/null +++ b/numpy/core/tests/test_argparse.py @@ -0,0 +1,62 @@ +""" +Tests for the private NumPy argument parsing functionality. +They mainly exists to ensure good test coverage without having to try the +weirder cases on actual numpy functions but test them in one place. + +The test function is defined in C to be equivalent to (errors may not always +match exactly, and could be adjusted): + + def func(arg1, /, arg2, *, arg3): + i = integer(arg1) # reproducing the 'i' parsing in Python. + return None +""" + +import pytest + +import numpy as np +from numpy.core._multiarray_tests import argparse_example_function as func + + +def test_invalid_integers(): + with pytest.raises(TypeError, + match="integer argument expected, got float"): + func(1.) + with pytest.raises(OverflowError): + func(2**100) + + +def test_missing_arguments(): + with pytest.raises(TypeError, + match="missing required positional argument 0"): + func() + with pytest.raises(TypeError, + match="missing required positional argument 0"): + func(arg2=1, arg3=4) + with pytest.raises(TypeError, + match=r"missing required argument \'arg2\' \(pos 1\)"): + func(1, arg3=5) + + +def test_too_many_positional(): + # the second argument is positional but can be passed as keyword. + with pytest.raises(TypeError, + match="takes from 2 to 3 positional arguments but 4 were given"): + func(1, 2, 3, 4) + + +def test_multiple_values(): + with pytest.raises(TypeError, + match=r"given by name \('arg2'\) and position \(position 1\)"): + func(1, 2, arg2=3) + + +def test_string_fallbacks(): + # We can (currently?) use numpy strings to test the "slow" fallbacks + # that should normally not be taken due to string interning. + arg2 = np.unicode_("arg2") + missing_arg = np.unicode_("missing_arg") + func(1, **{arg2: 3}) + with pytest.raises(TypeError, + match="got an unexpected keyword argument 'missing_arg'"): + func(2, **{missing_arg: 3}) + |