Skip to content

Commit 37c6555

Browse files
committed
obj: Handle user instance hash based on Python adhoc rules.
User instances are hashable by default (using __hash__ inherited from "object"). But if __eq__ is defined and __hash__ not defined in particular class, instance is not hashable.
1 parent 7667727 commit 37c6555

File tree

2 files changed

+33
-6
lines changed

2 files changed

+33
-6
lines changed

py/obj.c

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,20 +166,34 @@ mp_int_t mp_obj_hash(mp_obj_t o_in) {
166166
return mp_obj_tuple_hash(o_in);
167167
} else if (MP_OBJ_IS_TYPE(o_in, &mp_type_type)) {
168168
return (mp_int_t)o_in;
169-
} else if (MP_OBJ_IS_OBJ(o_in)) {
169+
} else if (mp_obj_is_instance_type(mp_obj_get_type(o_in))) {
170170
// if a valid __hash__ method exists, use it
171-
mp_obj_t hash_method[2];
172-
mp_load_method_maybe(o_in, MP_QSTR___hash__, hash_method);
173-
if (hash_method[0] != MP_OBJ_NULL) {
174-
mp_obj_t hash_val = mp_call_method_n_kw(0, 0, hash_method);
171+
mp_obj_t method[2];
172+
mp_load_method_maybe(o_in, MP_QSTR___hash__, method);
173+
if (method[0] != MP_OBJ_NULL) {
174+
mp_obj_t hash_val = mp_call_method_n_kw(0, 0, method);
175175
if (MP_OBJ_IS_INT(hash_val)) {
176176
return mp_obj_int_get_truncated(hash_val);
177177
}
178+
goto error;
178179
}
180+
181+
mp_load_method_maybe(o_in, MP_QSTR___eq__, method);
182+
if (method[0] == MP_OBJ_NULL) {
183+
// https://docs.python.org/3/reference/datamodel.html#object.__hash__
184+
// "User-defined classes have __eq__() and __hash__() methods by default;
185+
// with them, all objects compare unequal (except with themselves) and
186+
// x.__hash__() returns an appropriate value such that x == y implies
187+
// both that x is y and hash(x) == hash(y)."
188+
return (mp_int_t)o_in;
189+
}
190+
// "A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None.
191+
// When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError"
179192
}
180193

181-
// TODO hash class and instances - in CPython by default user created classes' __hash__ resolves to their id
194+
// TODO hash classes
182195

196+
error:
183197
if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) {
184198
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "unhashable type"));
185199
} else {

tests/basics/builtin_hash.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,16 @@ def __repr__(self):
1919

2020
print(hash(A()))
2121
print({A():1})
22+
23+
class B:
24+
pass
25+
hash(B())
26+
27+
28+
class C:
29+
def __eq__(self, another):
30+
return True
31+
try:
32+
hash(C())
33+
except TypeError:
34+
print("TypeError")

0 commit comments

Comments
 (0)