Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 28, 2015
Merged

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

merged 1 commit into from
Sep 28, 2015

Conversation

yashmehrotra
Copy link
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 ?

PyObject *zero = PyLong_FromLong(0);
PyObject *one = PyLong_FromLong(1);
PyObject *minus_one = PyLong_FromLong(-1);
Copy link
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
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
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
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
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
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
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.

@@ -325,7 +325,7 @@ PyObject_Cmp(PyObject *i1, PyObject *i2, int *cmp)
{
int v;
v = PyObject_RichCompareBool(i1, i2, Py_LT);
if (v == 0) {
if (v == 1) {
Copy link
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
Contributor Author

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

if (PyErr_Occurred()) {

/* In case of error in comparison */
if ( v == -1) {
Copy link
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
Contributor Author

@jaimefrio Extra space around parenthesis removed.

if (v == 1) {
ret = PyLong_FromLong(0);
}
}
Copy link
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
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
Contributor Author

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

v = PyObject_RichCompareBool(in1, zero, Py_EQ);
if (v == 1) {
ret = PyLong_FromLong(0);
}
Copy link
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
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
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
Contributor Author

@jaimefrio Any feedback for the error message ?

@yashmehrotra
Copy link
Contributor Author

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

@yashmehrotra
Copy link
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
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
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.

v = PyObject_RichCompareBool(in1, zero, Py_LT);
if (v == 1) {
ret = PyLong_FromLong(-1);
} else if (v == 0) {
Copy link
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
Contributor Author

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

if (v == 1) {
ret = PyLong_FromLong(-1);
goto done;
} else if (v == -1) {
Copy link
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
Contributor Author

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

@charris
Copy link
Member

charris commented Sep 27, 2015

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

Py_XDECREF(*out);
*out = ret;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Wrong endentation.

@charris
Copy link
Member

charris commented Sep 28, 2015

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
Contributor Author

@charris Changes updated. I hope its good now.

@charris
Copy link
Member

charris commented Sep 28, 2015

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
Contributor Author

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

Py_DECREF(zero);
return;

v = PyObject_RichCompareBool(in1, zero, Py_LT);
Copy link
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
Copy link
Member

charris commented Sep 28, 2015

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
Contributor Author

@charris All commits squashed into one.

@charris
Copy link
Member

charris commented Sep 28, 2015

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
Copy link
Member

charris commented Sep 28, 2015

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
Copy link
Member

charris commented Sep 29, 2015

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