ronantakizawa/codereview-bench huggingface dataset:
Repository: numpy/numpy
File: numpy/lib/tests/test_function_base.py (Python)

Code before:
import decimal
import math
import operator
import sys
import typing
import warnings
from fractions import Fraction
from functools import partial

import hypothesis
import hypothesis.strategies as st
import pytest
from hypothesis.extra.numpy import arrays

import numpy as np
import numpy.lib._function_base_impl as nfb
from numpy import (
    angle,
    average,
    bartlett,
    blackman,
    corrcoef,
    cov,
    delete,
    diff,
    digitize,
    extract,
    flipud,
    gradient,
    hamming,

Review comment (suggestion):
I don't think we use `typing` at all in any of our tests. The only place it seems to show up is in a vendored test from meson:

```
goldbaum at Nathans-MBP in ~/Documents/numpy on main
± rg "import typing" **/test_*.py
vendored-meson/meson/mesonbuild/scripts/test_loaded_modules.py
5:import typing as T
```

So probably best to just delete the type hints since it makes this code hard to read for someone who is used to looking at other tests in NumPy.

Code after:
import decimal
import math
import operator
import sys
import warnings
from fractions import Fraction
from functools import partial

import hypothesis
import hypothesis.strategies as st
import pytest
from hypothesis.extra.numpy import arrays

import numpy as np
import numpy.lib._function_base_impl as nfb
from numpy import (
    angle,
    average,
    bartlett,
    blackman,
    corrcoef,
    cov,
    delete,
    diff,
    digitize,
    extract,
    flipud,
    gradient,
    hamming,
    hanning,
Repository: numpy/numpy
File: numpy/_core/numerictypes.pyi (Python)

Code before:
    "uint",
    "float96",
    "float128",
    "complex192",
    "complex256",
]

@type_check_only
class _TypeCodes(TypedDict):
    Character: L['c']
    Integer: L['bhilqnp']
    UnsignedInteger: L['BHILQNP']
    Float: L['efdg']
    Complex: L['FDG']
    AllInteger: L['bBhHiIlLqQnNpP']
    AllFloat: L['efdgFDG']
    Datetime: L['Mm']
    All: L['?bhilqnpBHILQNPefdgFDGSUVOMm']

def isdtype(dtype: dtype | type[Any], kind: DTypeLike | tuple[DTypeLike, ...]) -> builtins.bool: ...

typecodes: Final[_TypeCodes] = ...
ScalarType: Final[
    tuple[
        type[int],
        type[float],
        type[complex],
        type[py_bool],
        type[bytes],
        type[str],
        type[memoryview[Any]],
        type[np.bool],
        type[complex64],
        type[complex128],
        type[complex128 | complex192 | complex256],
        type[float16],
        type[float32],
        type[float64],
        type[float64 | float96 | float128],
        type[int8],
        type[int16],
        type[int32],
        type[int32 | int64],
        type[int64],
        type[datetime64],
        type[timedelta64],
        type[object_],
        type[bytes_],
        type[str_],
        type[uint8],
        type[uint16],

Review comment (suggestion):
```suggestion
def issubdtype(arg1: DTypeLike, arg2: DTypeLike) -> builtins.bool: ...

typecodes: Final[_TypeCodes] = ...
```

Code after:
    "half",
    "bool_",
    "int_",
    "uint",
    "float96",
    "float128",
    "complex192",
    "complex256",
]

@type_check_only
class _TypeCodes(TypedDict):
    Character: L["c"]
    Integer: L["bhilqnp"]
    UnsignedInteger: L["BHILQNP"]
    Float: L["efdg"]
    Complex: L["FDG"]
    AllInteger: L["bBhHiIlLqQnNpP"]
    AllFloat: L["efdgFDG"]
    Datetime: L["Mm"]
    All: L["?bhilqnpBHILQNPefdgFDGSUVOMm"]

def isdtype(dtype: dtype | type, kind: DTypeLike | tuple[DTypeLike, ...]) -> py_bool: ...
def issubdtype(arg1: DTypeLike | None, arg2: DTypeLike | None) -> py_bool: ...

typecodes: Final[_TypeCodes] = ...
ScalarType: Final[
    tuple[
        type[int],
        type[float],
        type[complex],
        type[py_bool],
        type[bytes],
        type[str],
        type[memoryview[Any]],
        type[np.bool],
        type[complex64],
        type[complex128],
        type[complex128 | complex192 | complex256],
        type[float16],
        type[float32],
        type[float64],
        type[float64 | float96 | float128],
        type[int8],
        type[int16],
        type[int32],
        type[int32 | int64],
        type[int64],
        type[datetime64],
        type[timedelta64],
        type[object_],
Repository: numpy/numpy
File: numpy/_core/src/common/blas_utils.h (C/C++)

Code before:
#include "numpy/numpyconfig.h" // for NPY_VISIBILITY_HIDDEN

#include <stdbool.h>

/* 
 * NPY_BLAS_CHECK_FPE_SUPPORT controls whether we need a runtime check
 * for floating-point error (FPE) support in BLAS.
 * The known culprit right now is SVM likely only on mac, but that is not
 * quite clear.
 * This checks always on all ARM (it is a small check overall).
 */
#if defined(__aarch64__)
#define NPY_BLAS_CHECK_FPE_SUPPORT 1
#else
#define NPY_BLAS_CHECK_FPE_SUPPORT 0
#endif

/* Initialize BLAS environment, if needed
 */
NPY_VISIBILITY_HIDDEN int
npy_blas_init(void);

/* Runtime check if BLAS supports floating-point errors.
 * true  - BLAS supports FPE and one can rely on them to indicate errors
 * false - BLAS does not support FPE.  Special handling needed for FPE state
 */
NPY_VISIBILITY_HIDDEN bool
npy_blas_supports_fpe(void);

/* If BLAS supports FPE, exactly the same as npy_get_floatstatus_barrier().
 * Otherwise, we can't rely on FPE state and need special handling.
 */
NPY_VISIBILITY_HIDDEN int
npy_get_floatstatus_after_blas(void);


Review comment (bug):
I wasn't sure what to put here, so went with the broad `__aarch64__`.  We could add `__APPLE__` if we think this is largely Accelerate only (i.e. OpenBLAS considered this a bug on their side, but for now I am not sure...).

It's not like it is utterly terrible if we have the runtime check for the global...

@mattip can you test it on an M4, my M1 hits the checks, but wouldn't actually trigger the spurious FPEs.

Code after:
#include "numpy/numpyconfig.h" // for NPY_VISIBILITY_HIDDEN

#include <stdbool.h>

/* 
 * NPY_BLAS_CHECK_FPE_SUPPORT controls whether we need a runtime check
 * for floating-point error (FPE) support in BLAS.
 * The known culprit right now is SVM likely only on mac, but that is not
 * quite clear.
 * This checks always on all ARM (it is a small check overall).
 */
#if defined(__APPLE__) && defined(__aarch64__) && defined(HAVE_CBLAS)
#define NPY_BLAS_CHECK_FPE_SUPPORT 1
#else
#define NPY_BLAS_CHECK_FPE_SUPPORT 0
#endif

/* Runtime check if BLAS supports floating-point errors.
 * true  - BLAS supports FPE and one can rely on them to indicate errors
 * false - BLAS does not support FPE.  Special handling needed for FPE state
 */
NPY_VISIBILITY_HIDDEN bool
npy_blas_supports_fpe(void);

/* Allow setting the BLAS FPE flag from Python.*/
NPY_VISIBILITY_HIDDEN bool
npy_set_blas_supports_fpe(bool value);

/* If BLAS supports FPE, exactly the same as npy_get_floatstatus_barrier().
 * Otherwise, we can't rely on FPE state and need special handling.
 */
NPY_VISIBILITY_HIDDEN int
npy_get_floatstatus_after_blas(void);
Repository: numpy/numpy
File: numpy/_core/src/common/blas_utils.h (C/C++)

Code before:
#include "numpy/numpyconfig.h" // for NPY_VISIBILITY_HIDDEN

#include <stdbool.h>

/* 
 * NPY_BLAS_CHECK_FPE_SUPPORT controls whether we need a runtime check
 * for floating-point error (FPE) support in BLAS.
 * The known culprit right now is SVM likely only on mac, but that is not
 * quite clear.
 * This checks always on all ARM (it is a small check overall).
 */
#if defined(__aarch64__) && defined(HAVE_CBLAS)
#define NPY_BLAS_CHECK_FPE_SUPPORT 1
#else
#define NPY_BLAS_CHECK_FPE_SUPPORT 0
#endif

/* Runtime check if BLAS supports floating-point errors.
 * true  - BLAS supports FPE and one can rely on them to indicate errors
 * false - BLAS does not support FPE.  Special handling needed for FPE state
 */
NPY_VISIBILITY_HIDDEN bool
npy_blas_supports_fpe(void);

/* Allow setting the BLAS FPE flag from Python.*/
NPY_VISIBILITY_HIDDEN bool
npy_set_blas_supports_fpe(bool value);

/* If BLAS supports FPE, exactly the same as npy_get_floatstatus_barrier().
 * Otherwise, we can't rely on FPE state and need special handling.
 */
NPY_VISIBILITY_HIDDEN int
npy_get_floatstatus_after_blas(void);


Review comment (suggestion):
Let's leave this restricted to macos, we have not seen reports of problems with linux.
```suggestion
#if defined(__APPLE__) && defined(__aarch64__) && defined(HAVE_CBLAS)
```

Code after:
#include "numpy/numpyconfig.h" // for NPY_VISIBILITY_HIDDEN

#include <stdbool.h>

/* 
 * NPY_BLAS_CHECK_FPE_SUPPORT controls whether we need a runtime check
 * for floating-point error (FPE) support in BLAS.
 * The known culprit right now is SVM likely only on mac, but that is not
 * quite clear.
 * This checks always on all ARM (it is a small check overall).
 */
#if defined(__APPLE__) && defined(__aarch64__) && defined(HAVE_CBLAS)
#define NPY_BLAS_CHECK_FPE_SUPPORT 1
#else
#define NPY_BLAS_CHECK_FPE_SUPPORT 0
#endif

/* Runtime check if BLAS supports floating-point errors.
 * true  - BLAS supports FPE and one can rely on them to indicate errors
 * false - BLAS does not support FPE.  Special handling needed for FPE state
 */
NPY_VISIBILITY_HIDDEN bool
npy_blas_supports_fpe(void);

/* Allow setting the BLAS FPE flag from Python.*/
NPY_VISIBILITY_HIDDEN bool
npy_set_blas_supports_fpe(bool value);

/* If BLAS supports FPE, exactly the same as npy_get_floatstatus_barrier().
 * Otherwise, we can't rely on FPE state and need special handling.
 */
NPY_VISIBILITY_HIDDEN int
npy_get_floatstatus_after_blas(void);
Repository: numpy/numpy
File: numpy/_core/src/multiarray/convert.c (C)

Code before:
        memset(dest, 0, numbytes);
        
        /* Strides compute */
        npy_intp *strides = PyArray_malloc(PyArray_NDIM(self) * sizeof(npy_intp));
        if (strides == NULL) {
            Py_DECREF(ret);
            return PyErr_NoMemory();
        }

        npy_intp itemsize = PyArray_ITEMSIZE(self);

        if (order == NPY_CORDER) {
            /* C-order: last dimension varies fastest */
            npy_intp stride = itemsize;
            for (int i = PyArray_NDIM(self) - 1; i >= 0; i--) {
                strides[i] = stride;
                stride *= PyArray_DIM(self, i);
            }
        }
        else {
            /* Fortran-order: first dimension varies fastest */
            npy_intp stride = itemsize;
            for (int i = 0; i < PyArray_NDIM(self); i++) {
                strides[i] = stride;
                stride *= PyArray_DIM(self, i);
            }
        }

        Py_INCREF(PyArray_DESCR(self));
        /* Array view */
        PyArrayObject *dest_array = (PyArrayObject *)PyArray_NewFromDescr(
            &PyArray_Type,
            PyArray_DESCR(self),
            PyArray_NDIM(self),
            PyArray_DIMS(self),
            strides,
            dest,
            NPY_ARRAY_WRITEABLE,
            NULL
        );

        PyArray_free(strides);

        if (dest_array == NULL) {
            Py_DECREF(ret);
            return NULL;
        }

        PyArray_CLEARFLAGS(dest_array, NPY_ARRAY_OWNDATA);
        
        /* Copy directly from source to destination with proper ordering */

Review comment (suggestion):
You should't need any of this, `PyArray_NewFromDescr` should fill in the strides for you.

Code after:
        }

        Py_INCREF(PyArray_DESCR(self));
        /* Array view */
        PyArrayObject *dest_array = (PyArrayObject *)PyArray_NewFromDescr(
            &PyArray_Type,
            PyArray_DESCR(self),
            PyArray_NDIM(self),
            PyArray_DIMS(self),
            NULL, // strides
            dest,
            flags,
            NULL
        );

        if (dest_array == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        
        /* Copy directly from source to destination with proper ordering */
        if (PyArray_CopyInto(dest_array, self) < 0) {
            Py_DECREF(dest_array);
            Py_DECREF(ret);
            return NULL;
        }
        
        Py_DECREF(dest_array);
        return ret;

    }

    /* Non-contiguous, Has References and/or Init Path.  */
    PyArrayObject *contig = (PyArrayObject *)PyArray_Ravel(self, order);
    if (contig == NULL) {
        return NULL;
    }
    
    ret = PyBytes_FromStringAndSize(PyArray_DATA(contig), numbytes);
    Py_DECREF(contig);
    return ret;
}

/*NUMPY_API*/
NPY_NO_EXPORT int
PyArray_FillWithScalar(PyArrayObject *arr, PyObject *obj)
{

    if (PyArray_FailUnlessWriteable(arr, "assignment destination") < 0) {
        return -1;
    }
Repository: numpy/numpy
File: benchmarks/benchmarks/bench_core.py (Python)

Code before:
    def time_array_float_l1000(self):
        np.array(self.float_l1000)

    def time_array_float_l1000_dtype(self):
        np.array(self.float_l1000, dtype=self.float64_dtype)

    def time_array_float64_l1000(self):
        np.array(self.float64_l1000)

    def time_array_int_l1000(self):
        np.array(self.int_l1000)

    def time_array_l(self):
        np.array(self.l)

    def time_array_l_view(self):
        np.array(self.l_view)

    def time_can_cast(self):
        np.can_cast(self.l10x10, self.float64_dtype)

    def time_tobytes_noncontiguous(self):
        self.noncontig.tobytes()

    def time_tobytes_contiguous(self):
        np.ascontiguousarray(self.noncontig).tobytes()

    def time_can_cast_same_kind(self):
        np.can_cast(self.l10x10, self.float64_dtype, casting="same_kind")

    def time_vstack_l(self):
        np.vstack(self.l)

    def time_hstack_l(self):
        np.hstack(self.l)

    def time_dstack_l(self):
        np.dstack(self.l)

    def time_arange_100(self):
        np.arange(100)

    def time_zeros_100(self):
        np.zeros(100)

    def time_ones_100(self):
        np.ones(100)

    def time_empty_100(self):
        np.empty(100)


Review comment (suggestion):
I don't think we should time this.  You could use `.T` just directly in the non-contiguous version also (since the `.T` will be irrelevantly fast here).

Code after:
        np.array(self.float_l1000)

    def time_array_float_l1000_dtype(self):
        np.array(self.float_l1000, dtype=self.float64_dtype)

    def time_array_float64_l1000(self):
        np.array(self.float64_l1000)

    def time_array_int_l1000(self):
        np.array(self.int_l1000)

    def time_array_l(self):
        np.array(self.l)

    def time_array_l_view(self):
        np.array(self.l_view)

    def time_can_cast(self):
        np.can_cast(self.l10x10, self.float64_dtype)

    def time_tobytes_noncontiguous(self):
        self.arr.T.tobytes()

    def time_can_cast_same_kind(self):
        np.can_cast(self.l10x10, self.float64_dtype, casting="same_kind")

    def time_vstack_l(self):
        np.vstack(self.l)

    def time_hstack_l(self):
        np.hstack(self.l)

    def time_dstack_l(self):
        np.dstack(self.l)

    def time_arange_100(self):
        np.arange(100)

    def time_zeros_100(self):
        np.zeros(100)

    def time_ones_100(self):
        np.ones(100)

    def time_empty_100(self):
        np.empty(100)

    def time_empty_like(self):
        np.empty_like(self.l10x10)

    def time_eye_100(self):
Repository: numpy/numpy
File: numpy/_core/multiarray.py (Python)

Code before:
    dtype('uint8')

    >>> np.min_scalar_type(-260)
    dtype('int16')

    >>> np.min_scalar_type(3.1)
    dtype('float16')

    >>> np.min_scalar_type(1e50)
    dtype('float64')

    >>> np.min_scalar_type(np.arange(4,dtype='f8'))
    dtype('float64')

    """
    return (a,)


@array_function_from_c_func_and_dispatcher(_multiarray_umath.result_type)
def result_type(*arrays_and_dtypes):
    """
    result_type(*arrays_and_dtypes)

    Returns the type that results from applying the NumPy
    type promotion rules to the arguments
    (`see also <https://numpy.org/doc/stable/reference/arrays.promotion.html>`_).

    Parameters
    ----------
    arrays_and_dtypes : list of arrays and dtypes
        The operands of some operation whose result type is needed.

    Returns
    -------
    out : dtype
        The result type.

    See also
    --------
    dtype, promote_types, min_scalar_type, can_cast

    Examples
    --------
    >>> import numpy as np
    >>> np.result_type(3, np.arange(7, dtype='i1'))
    dtype('int8')

    >>> np.result_type('i4', 'c8')
    dtype('complex128')

    >>> np.result_type(3.0, -2)

Review comment (suggestion):
```suggestion
    :ref:`type promotion rules <arrays.promotion>` to the arguments
```

Code after:
    dtype('uint8')

    >>> np.min_scalar_type(-260)
    dtype('int16')

    >>> np.min_scalar_type(3.1)
    dtype('float16')

    >>> np.min_scalar_type(1e50)
    dtype('float64')

    >>> np.min_scalar_type(np.arange(4,dtype='f8'))
    dtype('float64')

    """
    return (a,)


@array_function_from_c_func_and_dispatcher(_multiarray_umath.result_type)
def result_type(*arrays_and_dtypes):
    """
    result_type(*arrays_and_dtypes)

    Returns the type that results from applying the NumPy
    :ref:`type promotion <arrays.promotion>` rules to the arguments.

    Parameters
    ----------
    arrays_and_dtypes : list of arrays and dtypes
        The operands of some operation whose result type is needed.

    Returns
    -------
    out : dtype
        The result type.

    See also
    --------
    dtype, promote_types, min_scalar_type, can_cast

    Examples
    --------
    >>> import numpy as np
    >>> np.result_type(3, np.arange(7, dtype='i1'))
    dtype('int8')

    >>> np.result_type('i4', 'c8')
    dtype('complex128')

    >>> np.result_type(3.0, -2)
    dtype('float64')
Repository: numpy/numpy
File: benchmarks/benchmarks/bench_indexing.py (Python)

Code before:
            arr[indx] = 5.

    def time_assign_cast(self, ndim):
        # time an assignment which may use a cast operation
        arr = self.array
        indx = (1,) * ndim
        val = np.int16(43)
        for i in range(100):
            arr[indx] = val


class BooleanAssignmentOrder(Benchmark):
    params = ['C', 'F']
    param_names = ['order']

    def setup(self, order):
        shape = (64, 64, 64)
        self.base = np.zeros(shape, dtype=np.uint32, order=order)
        # emulate gh-30156: boolean assignment into a sliced Fortran/C view
        self.view = self.base[1:-1, 1:-1, 1:-1]
        mask = np.random.RandomState(0).rand(*self.view.shape) > 0.5
        if order == 'F':
            mask = np.asfortranarray(mask)
        else:
            mask = np.ascontiguousarray(mask)
        self.mask = mask
        self.value = np.uint32(7)

    def time_boolean_assign_scalar(self, order):
        self.view[self.mask] = self.value


class IndexingSeparate(Benchmark):
    def setup(self):
        self.tmp_dir = mkdtemp()
        self.fp = memmap(pjoin(self.tmp_dir, 'tmp.dat'),
                         dtype=float32, mode='w+', shape=(50, 60))
        self.indexes = array([3, 4, 6, 10, 20])

    def teardown(self):
        del self.fp
        shutil.rmtree(self.tmp_dir)

    def time_mmap_slicing(self):
        for i in range(1000):
            self.fp[5:10]

    def time_mmap_fancy_indexing(self):
        for i in range(1000):
            self.fp[self.indexes]


Review comment (suggestion):
```suggestion
        self.mask = mask.copy(order)
```
or similar also works, not that it matters.

Code after:
            arr[indx] = 5.

    def time_assign_cast(self, ndim):
        # time an assignment which may use a cast operation
        arr = self.array
        indx = (1,) * ndim
        val = np.int16(43)
        for i in range(100):
            arr[indx] = val


class BooleanAssignmentOrder(Benchmark):
    params = ['C', 'F']
    param_names = ['order']

    def setup(self, order):
        shape = (64, 64, 64)
        # emulate gh-30156: boolean assignment into a Fortran/C array
        self.base = np.zeros(shape, dtype=np.uint32, order=order)
        mask = np.random.RandomState(0).rand(*self.base.shape) > 0.5
        self.mask = mask.copy(order)
        self.value = np.uint32(7)

    def time_boolean_assign_scalar(self, order):
        self.base[self.mask] = self.value


class IndexingSeparate(Benchmark):
    def setup(self):
        self.tmp_dir = mkdtemp()
        self.fp = memmap(pjoin(self.tmp_dir, 'tmp.dat'),
                         dtype=float32, mode='w+', shape=(50, 60))
        self.indexes = array([3, 4, 6, 10, 20])

    def teardown(self):
        del self.fp
        shutil.rmtree(self.tmp_dir)

    def time_mmap_slicing(self):
        for i in range(1000):
            self.fp[5:10]

    def time_mmap_fancy_indexing(self):
        for i in range(1000):
            self.fp[self.indexes]


class IndexingStructured0D(Benchmark):
    def setup(self):
        self.dt = np.dtype([('a', 'f4', 256)])
Repository: numpy/numpy
File: numpy/_core/_internal.py (Python)

Code before:

    # keyword only args depend on whether this is a gufunc
    kwargs = (
        ", casting='same_kind'"
        ", order='K'"
        ", dtype=None"
        ", subok=True"
    )

    # NOTE: gufuncs may or may not support the `axis` parameter
    if ufunc.signature is None:
        kwargs = f", where=True{kwargs}[, signature]"
    else:
        kwargs += "[, signature, axes, axis]"

    # join all the parts together
    return f'{ufunc.__name__}({in_args}{out_args}, *{kwargs})'


def _ufunc_inspect_signature_builder(ufunc):
    """
    Builds a ``__text_signature__`` string.

    Should be kept in sync with `_ufunc_doc_signature_formatter`.
    """

    from inspect import Parameter, Signature

    params = []

    # positional-only input parameters
    if ufunc.nin == 1:
        params.append(Parameter("x", Parameter.POSITIONAL_ONLY))
    else:
        params.extend(
            Parameter(f"x{i}", Parameter.POSITIONAL_ONLY)
            for i in range(1, ufunc.nin + 1)
        )

    # for the sake of simplicity, we only consider a single output parameter
    if ufunc.nout == 1:
        out_default = None
    else:
        out_default = (None,) * ufunc.nout
    params.append(
        Parameter("out", Parameter.POSITIONAL_OR_KEYWORD, default=out_default),
    )

    if ufunc.signature is None:
        params.append(Parameter("where", Parameter.KEYWORD_ONLY, default=True))


Review comment (suggestion):
I wondered whether there was any way to combine the two, but probably difficult and overkill. 

However, do also add a note to `_ufunc_doc_signature_formatter` that the code here should be kept in sync with this one.

Code after:
            default=repr((None,) * ufunc.nout)
        )

    # keyword only args depend on whether this is a gufunc
    kwargs = (
        ", casting='same_kind'"
        ", order='K'"
        ", dtype=None"
        ", subok=True"
    )

    # NOTE: gufuncs may or may not support the `axis` parameter
    if ufunc.signature is None:
        kwargs = f", where=True{kwargs}[, signature]"
    else:
        kwargs += "[, signature, axes, axis]"

    # join all the parts together
    return f'{ufunc.__name__}({in_args}{out_args}, *{kwargs})'


def _ufunc_inspect_signature_builder(ufunc):
    """
    Builds a ``__signature__`` string.

    Should be kept in sync with `_ufunc_doc_signature_formatter`.
    """

    from inspect import Parameter, Signature

    params = []

    # positional-only input parameters
    if ufunc.nin == 1:
        params.append(Parameter("x", Parameter.POSITIONAL_ONLY))
    else:
        params.extend(
            Parameter(f"x{i}", Parameter.POSITIONAL_ONLY)
            for i in range(1, ufunc.nin + 1)
        )

    # for the sake of simplicity, we only consider a single output parameter
    if ufunc.nout == 1:
        out_default = None
    else:
        out_default = (None,) * ufunc.nout
    params.append(
        Parameter("out", Parameter.POSITIONAL_OR_KEYWORD, default=out_default),
    )

    if ufunc.signature is None: