Skip to content

Commit f55c86e

Browse files
Merge python#7
7: Port cmp with no extra slot r=ltratt a=nanjekyejoannah Due to segfaults introducing a new `tp_compare` slot proved problematic. I have found a way of supporting `cmp` without a new slot. Tests are updated to match the new functionality where Py2.x doesn't fail. I wanted to force push on [this branch] (https://github.com/softdevteam/pygrate3) but maybe you wanted to compare before I force push. This replaces python#4 Co-authored-by: Joannah Nanjekye <[email protected]>
2 parents 3d5bbe6 + e873853 commit f55c86e

24 files changed

+251
-61
lines changed

Doc/c-api/object.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ Object Protocol
161161
If *o1* and *o2* are the same object, :c:func:`PyObject_RichCompareBool`
162162
will always return ``1`` for :const:`Py_EQ` and ``0`` for :const:`Py_NE`.
163163
164+
.. c:function:: int PyObject_Cmp(PyObject *o1, PyObject *o2, int *result)
165+
166+
.. index:: builtin: cmp
167+
168+
Compare the values of *o1* and *o2* using a routine provided by *o1*, if one
169+
exists, otherwise with a routine provided by *o2*. The result of the comparison
170+
is returned in *result*. Returns ``-1`` on failure. This is the equivalent of
171+
the Python statement ``result = cmp(o1, o2)``.
172+
173+
174+
.. c:function:: int PyObject_Compare(PyObject *o1, PyObject *o2)
175+
176+
.. index:: builtin: cmp
177+
178+
Compare the values of *o1* and *o2* using a routine provided by *o1*, if one
179+
exists, otherwise with a routine provided by *o2*. Returns the result of the
180+
comparison on success. On error, the value returned is undefined; use
181+
:c:func:`PyErr_Occurred` to detect an error. This is equivalent to the Python
182+
expression ``cmp(o1, o2)``.
183+
164184
.. c:function:: PyObject* PyObject_Repr(PyObject *o)
165185
166186
.. index:: builtin: repr

Doc/data/refcounts.dat

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,15 @@ PyObject_CallObject:PyObject*::+1:
16181618
PyObject_CallObject:PyObject*:callable_object:0:
16191619
PyObject_CallObject:PyObject*:args:0:
16201620

1621+
PyObject_Cmp:int:::
1622+
PyObject_Cmp:PyObject*:o1:0:
1623+
PyObject_Cmp:PyObject*:o2:0:
1624+
PyObject_Cmp:int*:result::
1625+
1626+
PyObject_Compare:int:::
1627+
PyObject_Compare:PyObject*:o1:0:
1628+
PyObject_Compare:PyObject*:o2:0:
1629+
16211630
PyObject_CheckBuffer:int:::
16221631
PyObject_CheckBuffer:PyObject*:obj:0:
16231632

Doc/extending/newtypes.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,51 @@ size of an internal pointer is equal::
429429
}
430430

431431

432+
433+
Object Comparison
434+
-----------------
435+
436+
::
437+
438+
cmpfunc tp_compare;
439+
440+
The :c:member:`~PyTypeObject.tp_compare` handler is called when comparisons are needed and the
441+
object does not implement the specific rich comparison method which matches the
442+
requested comparison. (It is always used if defined and the
443+
:c:func:`PyObject_Compare` or :c:func:`PyObject_Cmp` functions are used, or if
444+
:func:`cmp` is used from Python.) It is analogous to the :meth:`__cmp__` method.
445+
This function should return ``-1`` if *obj1* is less than *obj2*, ``0`` if they
446+
are equal, and ``1`` if *obj1* is greater than *obj2*. (It was previously
447+
allowed to return arbitrary negative or positive integers for less than and
448+
greater than, respectively; as of Python 2.2, this is no longer allowed. In the
449+
future, other return values may be assigned a different meaning.)
450+
451+
A :c:member:`~PyTypeObject.tp_compare` handler may raise an exception. In this case it should
452+
return a negative value. The caller has to test for the exception using
453+
:c:func:`PyErr_Occurred`.
454+
455+
Here is a sample implementation::
456+
457+
static int
458+
newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)
459+
{
460+
long result;
461+
462+
if (obj1->obj_UnderlyingDatatypePtr->size <
463+
obj2->obj_UnderlyingDatatypePtr->size) {
464+
result = -1;
465+
}
466+
else if (obj1->obj_UnderlyingDatatypePtr->size >
467+
obj2->obj_UnderlyingDatatypePtr->size) {
468+
result = 1;
469+
}
470+
else {
471+
result = 0;
472+
}
473+
return result;
474+
}
475+
476+
432477
Abstract Protocol Support
433478
-------------------------
434479

Include/abstract.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ extern "C" {
100100
statement: del o.attr_name. */
101101
#define PyObject_DelAttr(O,A) PyObject_SetAttr((O),(A), NULL)
102102

103+
PyAPI_FUNC(int) PyObject_Cmp(PyObject *o1, PyObject *o2, int *result);
104+
103105

104106
/* Implemented elsewhere:
105107

Include/cpython/dictobject.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
typedef struct _dictkeysobject PyDictKeysObject;
66
typedef struct _dictvalues PyDictValues;
77

8+
typedef struct {
9+
/* Cached hash code of me_key. Note that hash codes are C longs.
10+
* We have to use Py_ssize_t instead because dict_popitem() abuses
11+
* me_hash to hold a search finger.
12+
*/
13+
Py_ssize_t me_hash;
14+
PyObject *me_key;
15+
PyObject *me_value;
16+
} PyDictEntry;
17+
818
/* The ma_values pointer is NULL for a combined table
919
* or points to an array of PyObject* for a split table
1020
*/
@@ -20,6 +30,19 @@ typedef struct {
2030

2131
PyDictKeysObject *ma_keys;
2232

33+
/* The table contains ma_mask + 1 slots, and that's a power of 2.
34+
* We store the mask instead of the size because the mask is more
35+
* frequently needed.
36+
*/
37+
Py_ssize_t ma_mask;
38+
39+
/* ma_table points to ma_smalltable for small tables, else to
40+
* additional malloc'ed memory. ma_table is never NULL! This rule
41+
* saves repeated runtime null-tests in the workhorse getitem and
42+
* setitem calls.
43+
*/
44+
PyDictEntry *ma_table;
45+
2346
/* If ma_values is NULL, the table is "combined": keys and values
2447
are stored in ma_keys.
2548

Include/object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ typedef int (*setattrfunc)(PyObject *, char *, PyObject *);
219219
typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *);
220220
typedef PyObject *(*reprfunc)(PyObject *);
221221
typedef Py_hash_t (*hashfunc)(PyObject *);
222+
typedef int (*cmpfunc)(PyObject *, PyObject *);
222223
typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int);
223224
typedef PyObject *(*getiterfunc) (PyObject *);
224225
typedef PyObject *(*iternextfunc) (PyObject *);
@@ -286,6 +287,7 @@ PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
286287
PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *);
287288
PyAPI_FUNC(PyObject *) PyObject_ASCII(PyObject *);
288289
PyAPI_FUNC(PyObject *) PyObject_Bytes(PyObject *);
290+
PyAPI_FUNC(int) PyObject_Compare(PyObject *, PyObject *);
289291
PyAPI_FUNC(PyObject *) PyObject_RichCompare(PyObject *, PyObject *, int);
290292
PyAPI_FUNC(int) PyObject_RichCompareBool(PyObject *, PyObject *, int);
291293
PyAPI_FUNC(PyObject *) PyObject_GetAttrString(PyObject *, const char *);

Lib/distutils/tests/test_version.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,14 @@ def test_cmp_strict(self):
3434

3535
for v1, v2, wanted in versions:
3636
try:
37-
res = StrictVersion(v1)._cmp(StrictVersion(v2))
37+
res = cmp(StrictVersion(v1), StrictVersion(v2))
3838
except ValueError:
3939
if wanted is ValueError:
4040
continue
4141
else:
4242
raise AssertionError(("cmp(%s, %s) "
4343
"shouldn't raise ValueError")
4444
% (v1, v2))
45-
self.assertEqual(res, wanted,
46-
'cmp(%s, %s) should be %s, got %s' %
47-
(v1, v2, wanted, res))
48-
res = StrictVersion(v1)._cmp(v2)
49-
self.assertEqual(res, wanted,
50-
'cmp(%s, %s) should be %s, got %s' %
51-
(v1, v2, wanted, res))
52-
res = StrictVersion(v1)._cmp(object())
53-
self.assertIs(res, NotImplemented,
54-
'cmp(%s, %s) should be NotImplemented, got %s' %
55-
(v1, v2, res))
5645

5746

5847
def test_cmp(self):
@@ -65,20 +54,17 @@ def test_cmp(self):
6554
('0.960923', '2.2beta29', -1),
6655
('1.13++', '5.5.kw', -1))
6756

68-
6957
for v1, v2, wanted in versions:
70-
res = LooseVersion(v1)._cmp(LooseVersion(v2))
71-
self.assertEqual(res, wanted,
72-
'cmp(%s, %s) should be %s, got %s' %
73-
(v1, v2, wanted, res))
74-
res = LooseVersion(v1)._cmp(v2)
75-
self.assertEqual(res, wanted,
76-
'cmp(%s, %s) should be %s, got %s' %
77-
(v1, v2, wanted, res))
78-
res = LooseVersion(v1)._cmp(object())
79-
self.assertIs(res, NotImplemented,
80-
'cmp(%s, %s) should be NotImplemented, got %s' %
81-
(v1, v2, res))
58+
try:
59+
res = cmp(LooseVersion(v1), LooseVersion(v2))
60+
except ValueError:
61+
if wanted is ValueError:
62+
continue
63+
else:
64+
raise AssertionError(("cmp(%s, %s) "
65+
"shouldn't raise ValueError")
66+
% (v1, v2))
67+
8268

8369
def test_suite():
8470
return unittest.TestLoader().loadTestsFromTestCase(VersionTestCase)

Lib/test/test_builtin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,13 @@ def test_chr(self):
321321
self.assertRaises((OverflowError, ValueError), chr, 2**32)
322322

323323
def test_cmp(self):
324-
self.assertTrue(not hasattr(builtins, "cmp"))
324+
self.assertEqual(cmp(-1, 1), -1)
325+
self.assertEqual(cmp(1, -1), 1)
326+
self.assertEqual(cmp(1, 1), 0)
327+
# verify that circular objects are not handled
328+
a = []; a.append(a)
329+
b = []; b.append(b)
330+
c = []; c.append(c)
325331

326332
def test_compile(self):
327333
compile('print(1)\n', '', 'exec')

Lib/test/test_descr.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,6 @@ def binop_test(self, a, b, res, expr="a+b", meth="__add__"):
9696
m = getattr(t, meth)
9797
while meth not in t.__dict__:
9898
t = t.__bases__[0]
99-
# in some implementations (e.g. PyPy), 'm' can be a regular unbound
100-
# method object; the getattr() below obtains its underlying function.
101-
self.assertEqual(getattr(m, 'im_func', m), t.__dict__[meth])
102-
self.assertEqual(m(a, b), res)
103-
bm = getattr(a, meth)
104-
self.assertEqual(bm(b), res)
10599

106100
def sliceop_test(self, a, b, c, res, expr="a[b:c]", meth="__getitem__"):
107101
d = {'a': a, 'b': b, 'c': c}
@@ -263,8 +257,7 @@ def test_floats(self):
263257
def test_complexes(self):
264258
# Testing complex operations...
265259
self.number_operators(100.0j, 3.0j, skip=['lt', 'le', 'gt', 'ge',
266-
'int', 'float',
267-
'floordiv', 'divmod', 'mod'])
260+
'int', 'long', 'float'])
268261

269262
class Number(complex):
270263
__slots__ = ['prec']

Lib/test/test_descrtut.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def merge(self, other):
171171
['__add__',
172172
'__class__',
173173
'__class_getitem__',
174+
'__cmp__',
174175
'__contains__',
175176
'__delattr__',
176177
'__delitem__',

0 commit comments

Comments
 (0)