Skip to content

MAINT: Deprecated PyObject_Compare in favor of PyObject_RichCompareBool. Fixes #6265 - #6269

Merged
charris merged 1 commit into
numpy:masterfrom
yashmehrotra:depr-pyobject_compare
Sep 28, 2015
Merged

charris merged 1 commit into
numpy:masterfrom
yashmehrotra:depr-pyobject_compare

Conversation

@yashmehrotra

Copy link
Copy Markdown
Contributor

@jaimefrio I made the changes as discussed in #6265 . I ran all tests for Python2 and Python3 through python runtests.py / python3 runtests.py and did not encounter any failure.

As far as the PyObject_Cmp function in npy_3kcompat.h goes, I did not change that. I although did fix the only part of code base that uses it with PyObject_RichCompareBool.
As far as PyObject_Cmp goes, should I remove it or add a deprecation warning ?

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either one or minus_one is not going to be returned, and should therefore have its refcount decreased before returning, similarly to what happens with zero.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should I decrease both one and minus_one's refcount in the end ?
Like

if (is_gt == 1) {
    ret = one;
} else if (is_eq == 1) {
    ret = zero;
} else {
    ret = minus_one;
}

if (PyErr_Occurred()) {
    Py_DECREF(zero);
    return;
}
Py_XDECREF(*out);
*out = ret;
}
Py_DECREF(zero);
Py_DECREF(one);
Py_DECREF(minus_one);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All except the one you assign to out.

@jaimefrio

Copy link
Copy Markdown
Member

As commented inline, fixing this will also close #6229, so please add a "closes #6229" somewhere in the commit message. And if you could also add a test for that failure it would be perfect.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio I didn't quite understand the test part, would I be checking for #6229 problem in the test case ?

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio For reference count, I removed the one and minus_one as they were more of noise than signal. For issue #6229 , I did add the fix and the test case, although there is not much use of the fix as PyObject_Cmp is now not being used anywhere.
Are there any other changes I need to make to make this PR merge-ready ?

@jaimefrio

Copy link
Copy Markdown
Member

The body of the OBJECT_sign function should be something like this:

PyObject *zero = PyLong_FromLong(0);

UNARY_LOOP {
    PyObject *in1 = *(PyObject **)ip1;
    PyObject **out = (PyObject **)op1;
    PyObject *ret = NULL;
    int v;

    if (in1 == NULL) {
        in1 = Py_None;
    }
    v = PyObject_RichCompareBool(in1, zero, Py_LT);
    if (v == 1) {
        ret = PyLong_FromLong(-1);
    }
    else if (v == 0) {
        v = PyObject_RichCompareBool(in1, zero, Py_GT);
        if (v == 1) {
            ret = PyLong_FromLong(1);
        }
        else if (v == 0) {
            v = PyObject_RichCompareBool(in1, zero, Py_EQ);
            if (v == 1) {
                ret = PyLong_FromLong(0);
            }
        }
    }
    if (v < 0) {
        Py_DECREF(zero);
        return;
    }
    Py_XDECREF(*out);
    *out = ret;
}
Py_DECREF(zero);
}

Feel free to rewrite it as you see fit, but beware of the subtleties: if we cannot get a positive comparison using Py_LT, Py_GT or Py_EQ, it is considered an error and we bail out early.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two more instances of this mistake, right after the next two calls to PyObject_RichCompareBool below.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio I have the changes as suggested by you. Thanks for the help. Are there any other things I need to do ?

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space inside parenthesis.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio Extra space around parenthesis removed.

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a loophole in this logic. Consider the following:

>>> a = np.array([np.nan], dtype=object)
>>> a < 0
array([False], dtype=bool)
>>> a == 0
array([False], dtype=bool)
>>> a > 0
array([False], dtype=bool)

If we have an object that is neither larger, nor smaller, nor equal to 0, right now we will set the output to NULL, which I am not sure is what we really want. Perhaps None is the right thing to set it to, but I'll ask on the list for the community wisdom on this one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaimefrio I read a bit about NaN. In my opinion, comparing integers with NaN should return False as the comparison operators result is a boolean, and the comparison wouldn't be True.
Also, in JavaScript, all integer comparisons with NaN evaluates to false.
Well, that was my opinion. If it is decided to change the result to None as per the community decision,do keep updated on this one.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio Hi Jaime, do you have any updates for this PR ?

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so according to what seems to be the closest to a consensus, here we should have an else if (v == 0) that raises a TypeError, not sure what the right message should be. Thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaimefrio I have already implemented the above and its working as expected. For the error message, I was gonna ask you, what it should be 😛 .

How about we go with the error message you suggested in the mailing list or how about we just explicitly tell the user that the exception is due to NaN. Like TypeError: Trying to compare NaN (Something like this).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaimefrio Any thoughts on the error message or should I send a PR with error message TypeError: unorderable types ...etc

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio Any feedback for the error message ?

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@jaimefrio Can you please take a look at the new commit ?

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris Hi Charles, it seems that Jaime is busy these days and I couldn't get any feedback on this PR. Can you please take a look at this.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris @jaimefrio How about merging this PR, its been pending for days. Is there any work required by my side to make it merge ready ?

@ahaldane

Copy link
Copy Markdown
Member

Just to note, there is a possibility of overhauling OBJECT_sign in #6320 in the future. (But it may take a long time since that's waiting on __numpy_ufunc__ decisions). The implementation there takes care of complex numbers and float/complex nan more carefully.

However, this PR also has good cleanups for OBJECT_compare and _equivalent_fields (which there are no other plans to change), and I think the upgrade to OBJECT_sign in this PR is good to have in the short term.

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be inclined to do this like

        v = PyObject_RichCompareBool(in1, zero, Py_LT);
        if (v == 1) {
            ret = PyLong_FromLong(-1);
            goto done;
        }
        else if (v == -1) {
            goto error;
        }

and repeat with minor variations for the other two cases. That avoids the deep indentations and is, IMHO, easier to follow.
Note the style for else.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris I have made the changes as per your suggestions.

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note style guide doc/C_STYLE_GUIDE.rst.txt. The else should be like

}
else if {

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris I have modified the code as required. I hope its all good now.

@charris

charris commented Sep 27, 2015

Copy link
Copy Markdown
Member

I apologize for getting a bit confused myself. I'll take another look later this evening.

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong endentation.

@charris

charris commented Sep 28, 2015

Copy link
Copy Markdown
Member

LGTM modulo nitpick. Another possibility that adds no net lines and get rid of the goto is to replace

            ret = PyLong_FromLong(0);
            goto done;

by

            Py_XDECREF(*out)
            *out = PyLong_FromLong(0);
            continue;

etc.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris Changes updated. I hope its good now.

@charris

charris commented Sep 28, 2015

Copy link
Copy Markdown
Member

OK, my bad. The other errors need checking. How about

NPY_NO_EXPORT void
OBJECT_sign(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func))
{
    PyObject *zero = PyLong_FromLong(0);

    UNARY_LOOP {
        PyObject *in1 = *(PyObject **)ip1;
        PyObject **out = (PyObject **)op1;
        PyObject *ret = NULL;
        int v = 0;

        if (in1 == NULL) {
            in1 = Py_None;
        }

        if ((v = PyObject_RichCompareBool(in1, zero, Py_LT)) == 1) {
            ret = PyLong_FromLong(-1);
        }
        else if (v == 0 &&
                (v = PyObject_RichCompareBool(in1, zero, Py_GT)) == 1) {
            ret = PyLong_FromLong(1);
        }
        else if (v == 0 &&
                (v = PyObject_RichCompareBool(in1, zero, Py_EQ)) == 1) {
            ret = PyLong_FromLong(0);
        }
        else if (v == 0) {
            /* in1 is NaN */
            PyErr_SetString(PyExc_TypeError,
                    "unorderable types for comparison");
        }

        if (ret == NULL) {
            break;
        }
        Py_XDECREF(*out);
        *out = ret;
    }
    Py_DECREF(zero);
}

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris I have updated the code. Hope its all good to go now.

Comment thread numpy/core/src/umath/loops.c.src Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this line.

@charris

charris commented Sep 28, 2015

Copy link
Copy Markdown
Member

Almost there. When you are ready, squash the commits using git rebase -i HEAD~n, where n is the number of commits in this PR, then do a force push git push --force origin HEAD.

@yashmehrotra

Copy link
Copy Markdown
Contributor Author

@charris All commits squashed into one.

@charris

charris commented Sep 28, 2015

Copy link
Copy Markdown
Member

LGTM pending tests.

charris added a commit that referenced this pull request Sep 28, 2015
MAINT: Deprecated PyObject_Compare in favor of PyObject_RichCompareBool. Fixes #6265
@charris
charris merged commit f43d691 into numpy:master Sep 28, 2015
@charris

charris commented Sep 28, 2015

Copy link
Copy Markdown
Member

Thanks @yashmehrotra . If you would like to make a PR against the maintenance/1.10.x branch that just fixes PyObject_Cmp and add tests for same I would appreciate it. If not, I will do it later today.

@charris

charris commented Sep 29, 2015

Copy link
Copy Markdown
Member

I've backported the PyObject_Cmp fix, so don't do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants