Skip to content

Commit fd01d79

Browse files
committed
(arre, arigo) SF bug #1350060
Give a consistent behavior for comparison and hashing of method objects (both user- and built-in methods). Now compares the 'self' recursively. The hash was already asking for the hash of 'self'.
1 parent 996710f commit fd01d79

4 files changed

Lines changed: 81 additions & 11 deletions

File tree

Lib/test/test_class.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,37 @@ class I:
368368
pass
369369
else:
370370
print "attribute error for I.__init__ got masked"
371+
372+
373+
# Test comparison and hash of methods
374+
class A:
375+
def __init__(self, x):
376+
self.x = x
377+
def f(self):
378+
pass
379+
def g(self):
380+
pass
381+
def __eq__(self, other):
382+
return self.x == other.x
383+
def __hash__(self):
384+
return self.x
385+
class B(A):
386+
pass
387+
388+
a1 = A(1)
389+
a2 = A(2)
390+
assert a1.f == a1.f
391+
assert a1.f != a2.f
392+
assert a1.f != a1.g
393+
assert a1.f == A(1).f
394+
assert hash(a1.f) == hash(a1.f)
395+
assert hash(a1.f) == hash(A(1).f)
396+
397+
assert A.f != a1.f
398+
assert A.f != A.g
399+
assert B.f == A.f
400+
assert hash(B.f) == hash(A.f)
401+
402+
# the following triggers a SystemError in 2.4
403+
a = A(hash(A.f.im_func)^(-1))
404+
hash(a.f)

Lib/test/test_descr.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4014,11 +4014,24 @@ def methodwrapper():
40144014

40154015
l = []
40164016
vereq(l.__add__, l.__add__)
4017-
verify(l.__add__ != [].__add__)
4017+
vereq(l.__add__, [].__add__)
4018+
verify(l.__add__ != [5].__add__)
4019+
verify(l.__add__ != l.__mul__)
40184020
verify(l.__add__.__name__ == '__add__')
40194021
verify(l.__add__.__self__ is l)
40204022
verify(l.__add__.__objclass__ is list)
40214023
vereq(l.__add__.__doc__, list.__add__.__doc__)
4024+
try:
4025+
hash(l.__add__)
4026+
except TypeError:
4027+
pass
4028+
else:
4029+
raise TestFailed("no TypeError from hash([].__add__)")
4030+
4031+
t = ()
4032+
t += (7,)
4033+
vereq(t.__add__, (7,).__add__)
4034+
vereq(hash(t.__add__), hash((7,).__add__))
40224035

40234036
def notimplemented():
40244037
# all binary methods should be able to return a NotImplemented

Objects/classobject.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,9 +2221,17 @@ instancemethod_dealloc(register PyMethodObject *im)
22212221
static int
22222222
instancemethod_compare(PyMethodObject *a, PyMethodObject *b)
22232223
{
2224-
if (a->im_self != b->im_self)
2224+
int cmp;
2225+
cmp = PyObject_Compare(a->im_func, b->im_func);
2226+
if (cmp)
2227+
return cmp;
2228+
2229+
if (a->im_self == b->im_self)
2230+
return 0;
2231+
if (a->im_self == NULL || b->im_self == NULL)
22252232
return (a->im_self < b->im_self) ? -1 : 1;
2226-
return PyObject_Compare(a->im_func, b->im_func);
2233+
else
2234+
return PyObject_Compare(a->im_self, b->im_self);
22272235
}
22282236

22292237
static PyObject *
@@ -2299,7 +2307,10 @@ instancemethod_hash(PyMethodObject *a)
22992307
y = PyObject_Hash(a->im_func);
23002308
if (y == -1)
23012309
return -1;
2302-
return x ^ y;
2310+
x = x ^ y;
2311+
if (x == -1)
2312+
x = -2;
2313+
return x;
23032314
}
23042315

23052316
static int

Objects/descrobject.c

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -901,16 +901,28 @@ wrapper_dealloc(wrapperobject *wp)
901901
static int
902902
wrapper_compare(wrapperobject *a, wrapperobject *b)
903903
{
904-
if (a->descr == b->descr) {
905-
if (a->self == b->self)
906-
return 0;
907-
else
908-
return (a->self < b->self) ? -1 : 1;
909-
}
904+
if (a->descr == b->descr)
905+
return PyObject_Compare(a->self, b->self);
910906
else
911907
return (a->descr < b->descr) ? -1 : 1;
912908
}
913909

910+
static long
911+
wrapper_hash(wrapperobject *wp)
912+
{
913+
int x, y;
914+
x = _Py_HashPointer(wp->descr);
915+
if (x == -1)
916+
return -1;
917+
y = PyObject_Hash(wp->self);
918+
if (y == -1)
919+
return -1;
920+
x = x ^ y;
921+
if (x == -1)
922+
x = -2;
923+
return x;
924+
}
925+
914926
static PyObject *
915927
wrapper_repr(wrapperobject *wp)
916928
{
@@ -1008,7 +1020,7 @@ static PyTypeObject wrappertype = {
10081020
0, /* tp_as_number */
10091021
0, /* tp_as_sequence */
10101022
0, /* tp_as_mapping */
1011-
0, /* tp_hash */
1023+
(hashfunc)wrapper_hash, /* tp_hash */
10121024
(ternaryfunc)wrapper_call, /* tp_call */
10131025
0, /* tp_str */
10141026
PyObject_GenericGetAttr, /* tp_getattro */

0 commit comments

Comments
 (0)