Repository: numpy/numpy
File: numpy/_core/src/umath/ufunc_object.c (C)

Code before:
    }
    memset(scratch_objs, 0, sizeof(void *) * (nop * 4 + 2));
    
    PyArray_DTypeMeta **signature = (PyArray_DTypeMeta **)scratch_objs;
    PyArrayObject **operands = (PyArrayObject **)(signature + nop);
    PyArray_DTypeMeta **operand_DTypes = (PyArray_DTypeMeta **)(operands + nop + 1);
    PyArray_Descr **operation_descrs = (PyArray_Descr **)(operand_DTypes + nop);

    /*
     * Note that the input (and possibly output) arguments are passed in as
     * positional arguments. We extract these first and check for `out`
     * passed by keyword later.
     * Outputs and inputs are stored in `full_args.in` and `full_args.out`
     * as tuples (or NULL when no outputs are passed).
     */

    /* Check number of arguments */
    if (NPY_UNLIKELY((len_args < nin) || (len_args > nop))) {
        PyErr_Format(PyExc_TypeError,
                "%s() takes from %d to %d positional arguments but "
                "%zd were given",
                ufunc_get_name_cstr(ufunc) , nin, nop, len_args);
        goto fail;
    }
    /* Extra positional args but *no* keywords? */
    if (len_args > nin && (kwnames == NULL || PyTuple_GET_SIZE(kwnames) == 0)) {
        PyObject *extra = args[nin];

        /* legal out? -> skip */
        if (PyArray_Check(extra) || extra == Py_None ||
            _is_sequence_of_arrays_or_None(extra)) {
            /* it's a genuine out array/tuple – do nothing */
        }
        /* otherwise, if this is np.maximum, raise */
        else if (ufunc == UFUNC_MAXIMUM) {
            PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
                "Passing more than 2 positional arguments to np.maximum "
                "is deprecated; use out=keyword or np.maximum.reduce.");
            PyErr_Format(PyExc_TypeError,
                "np.maximum() takes exactly 2 input arguments (%zd given).",
                len_args);
            goto fail;
        }
    }

 
    /* Fetch input arguments. */
    full_args.in = PyArray_TupleFromItems(ufunc->nin, args, 0);
    if (full_args.in == NULL) {
        goto fail;
    }

Review comment (suggestion):
Maybe marten suggested it, but no, this isn't the right place, please look around a bit (further down).

Code after:
     * as tuples (or NULL when no outputs are passed).
     */

    /* Check number of arguments */
    if (NPY_UNLIKELY((len_args < nin) || (len_args > nop))) {
        const char *verb = (len_args == 1) ? "was" : "were";
        PyErr_Format(PyExc_TypeError,
            "%s() takes from %d to %d positional arguments but "
            "%zd %s given",
            ufunc_get_name_cstr(ufunc), nin, nop, len_args, verb);
        goto fail;
    }

    /* Fetch input arguments. */
    full_args.in = PyArray_TupleFromItems(ufunc->nin, args, 0);
    if (full_args.in == NULL) {
        goto fail;
    }

    /*
     * If there are more arguments, they define the out args. Otherwise
     * full_args.out is NULL for now, and the `out` kwarg may still be passed.
     */
    npy_bool out_is_passed_by_position = len_args > nin;
    if (out_is_passed_by_position) {
        npy_bool all_none = NPY_TRUE;

        full_args.out = PyTuple_New(nout);
        if (full_args.out == NULL) {
            goto fail;
        }
        for (int i = nin; i < nop; i++) {
            PyObject *tmp;
            if (i < (int)len_args) {
                tmp = args[i];
                if (tmp == Py_Ellipsis) {
                    PyErr_SetString(PyExc_TypeError,
                        "out=... is only allowed as a keyword argument.");
                    goto fail;
                }
                if (tmp != Py_None) {
                    all_none = NPY_FALSE;
                }
            }
            else {
                tmp = Py_None;
            }
            Py_INCREF(tmp);
            PyTuple_SET_ITEM(full_args.out, i-nin, tmp);
        }