Repository: numpy/numpy
File: numpy/_core/_internal.py (Python)

Code before:
            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))

    params.extend((
        Parameter("casting", Parameter.KEYWORD_ONLY, default='same_kind'),
        Parameter("order", Parameter.KEYWORD_ONLY, default='K'),
        Parameter("dtype", Parameter.KEYWORD_ONLY, default=None),
        Parameter("subok", Parameter.KEYWORD_ONLY, default=True),
        Parameter("signature", Parameter.KEYWORD_ONLY, default=None),
    ))

    if ufunc.signature is not None:
        # some gufuncs additionally support an `axis` parameters
        params.append(Parameter("kwargs", Parameter.VAR_KEYWORD))

    return Signature(params)


def npy_ctypes_check(cls):
    # determine if a class comes from ctypes, in order to work around
    # a bug in the buffer protocol for those objects, bpo-10746
    try:
        # ctypes class are new-style, so have an __mro__. This probably fails
        # for ctypes classes with multiple inheritance.
        if IS_PYPY:
            # (..., _ctypes.basics._CData, Bufferable, object)
            ctype_base = cls.__mro__[-3]
        else:
            # # (..., _ctypes._CData, object)
            ctype_base = cls.__mro__[-2]
        # right now, they're part of the _ctypes module
        return '_ctypes' in ctype_base.__module__
    except Exception:
        return False

# used to handle the _NoValue default argument for na_object
# in the C implementation of the __reduce__ method for stringdtype
def _convert_to_stringdtype_kwargs(coerce, na_object=_NoValue):

Review comment (suggestion):
Note that all `gufunc` support `axes` and `keepdims`, while `axis` can be used only if there is only a single core dimension -- see https://numpy.org/doc/stable/reference/ufuncs.html

Maybe overall it makes most sense to just add those explicitly above, as an `else` clause to `ufunc.signature is None` -- they are probably more useful to see than `casting`, etc. (and it is also the order in the docs). The one annoyance is that while `keepdims=False` by default, `axis` and `axes` don't really have a default - it is their presence that triggers examination; maybe `np._NoValue`?  (At least for `axis`, that leaves the option to at some point in the future make `axis=None` mean `ravel` like for reductions...) Note that probably the same is true for things like `signature`, so maybe I'm overthinking.



Code after:
    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))
    else:
        # NOTE: not all gufuncs support the `axis` parameters
        params.append(Parameter("axes", Parameter.KEYWORD_ONLY, default=_NoValue))
        params.append(Parameter("axis", Parameter.KEYWORD_ONLY, default=_NoValue))
        params.append(Parameter("keepdims", Parameter.KEYWORD_ONLY, default=False))

    params.extend((
        Parameter("casting", Parameter.KEYWORD_ONLY, default='same_kind'),
        Parameter("order", Parameter.KEYWORD_ONLY, default='K'),
        Parameter("dtype", Parameter.KEYWORD_ONLY, default=None),
        Parameter("subok", Parameter.KEYWORD_ONLY, default=True),
        Parameter("signature", Parameter.KEYWORD_ONLY, default=None),
    ))

    return Signature(params)


def npy_ctypes_check(cls):
    # determine if a class comes from ctypes, in order to work around
    # a bug in the buffer protocol for those objects, bpo-10746
    try:
        # ctypes class are new-style, so have an __mro__. This probably fails
        # for ctypes classes with multiple inheritance.
        if IS_PYPY:
            # (..., _ctypes.basics._CData, Bufferable, object)
            ctype_base = cls.__mro__[-3]
        else:
            # # (..., _ctypes._CData, object)
            ctype_base = cls.__mro__[-2]
        # right now, they're part of the _ctypes module
        return '_ctypes' in ctype_base.__module__
    except Exception:
        return False