$ pythonPython 3.9.6 (default, Jun 29 2021, 00:00:00)[GCC 11.1.1 20210531 (Red Hat 11.1.1-3)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> hash(-1)-2>>> hash(-2)-2>>> hash(-1) == hash(-2)True
>>> hash(1)1>>> hash(0)0>>> hash(3)3>>> hash(-4)-4
-1
…获取源代码
README.rst
也指向了 Github 上的 CPython。git clone https://github.com/python/cpython --depth 1
--depth 1
参数使 git
只获取有限的历史记录。这样可以让克隆操作快很多。如果之后需要完整的历史记录,我们可以再获取。让我们深入研究
hash
函数的文档?我们可以用 help(hash)
来查看文档内容:>>> hash<built-in function hash>>>> help(hash)Help on built-in function hash in module builtins:hash(obj, /) Return the hash value for the given object. Two objects that compare equal must also have the same hash value, but the reverse is not necessarily true.
hash()
的实现:$ grep -r 'Return the hash value'Python/clinic/bltinmodule.c.h:"Return the hash value for the given object.\n"Python/bltinmodule.c:Return the hash value for the given object.Doc/library/functions.rst: Return the hash value of the object (if it has one). Hash values areLib/hmac.py: """Return the hash value of this hashing object.
hmac
可能与加密的 HMAC 实现有关,所以我们可以忽略它。functions.rst
是一个文档文件,所以也可以忽略。Python/bltinmodule.c
看起来很有趣。如果我们查看这个文件,会找到这样一段代码:/*...Return the hash value for the given object.Two objects that compare equal must also have the same hash value, but thereverse is not necessarily true.[clinic start generated code]*/static PyObject *builtin_hash(PyObject *module, PyObject *obj)/*[clinic end generated code: output=237668e9d7688db7 input=58c48be822bf9c54]*/{ Py_hash_t x; x = PyObject_Hash(obj); if (x == -1) return NULL; return PyLong_FromSsize_t(x);}
- 我们调用
PyObject_Hash
来获得一个对象的哈希值如果计算出的哈希值是 -1,那表示是一个错误- 看起来我们用 -1 来表示错误,所以没有哈希函数会为真实对象计算出 -1
Py_Ssize_t
转换为 PyLongObject
(文档中称之为:“这是 PyObject 的子类型,表示一个 Python 整数对象”)hash(0)
是 0
,hash(1)
是 1
,hash(-2)
是 -2
,但 hash(-1)
不是 -1
。这是因为 -1
在内部被用来表示错误。hash(-1)
是 -2
呢?是什么将它设置成了这个值?PyObject_Hash
。让我们搜索一下。$ ag PyObject_Hash...Objects/rangeobject.c552: result = PyObject_Hash(t);Objects/object.c777:PyObject_HashNotImplemented(PyObject *v)785:PyObject_Hash(PyObject *v)802: return PyObject_HashNotImplemented(v);Objects/classobject.c307: y = PyObject_Hash(a->im_func);538: y = PyObject_Hash(PyInstanceMethod_GET_FUNCTION(self));...
Objects/object.c
中:Py_hash_tPyObject_Hash(PyObject *v){ PyTypeObject *tp = Py_TYPE(v); if (tp->tp_hash != NULL) return (*tp->tp_hash)(v); /* 为了保持通用做法:在 C 代码中仅从 object 继承的类型,应该无需显式调用 PyType_Ready 就能工作, * 我们在这里隐式调用 PyType_Ready,然后再次检查 tp_hash 槽 */ if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) return -1; if (tp->tp_hash != NULL) return (*tp->tp_hash)(v); } /* Otherwise, the object can't be hashed */ return PyObject_HashNotImplemented(v);}
Py_TYPE
)。然后寻找该类型的 tp_hash
函数,并在 v 上调用该函数:(*tp->tp_hash)(v)
-1
的 tp_hash
呢?让我们再次搜索 tp_hash
:$ ag tp_hash -l...Modules/_multiprocessing/semaphore.cObjects/sliceobject.cObjects/moduleobject.cObjects/exceptions.cModules/_pickle.cObjects/frameobject.cObjects/setobject.cObjects/rangeobject.cObjects/longobject.cObjects/object.cObjects/methodobject.cObjects/classobject.cObjects/enumobject.cObjects/odictobject.cObjects/complexobject.c...
PyLongObject
的说明(“这个…表示一个 Python 整数对象”),我先查看下 Objects/longobject.c
:PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "int", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ long_to_decimal_string, /* tp_repr */ &long_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)long_hash, /* tp_hash */ ...
PyLongObject
类型对象的 tp_hash
是 long_hash
。让我们看看这个函数。static Py_hash_tlong_hash(PyLongObject *v){ Py_uhash_t x; Py_ssize_t i; int sign; ... if (x == (Py_uhash_t)-1) x = (Py_uhash_t)-2; return (Py_hash_t)x;}
-1
被保留用作错误信号,所以代码明确地将该返回值转换为 -2
!hash(-1)
最终与 hash(-2)
相同。这不是一个彩蛋,只是为了避免使用 -1
作为 hash()
方法的返回值,因此采取的变通方法。这是正确/完整的答案吗?
Python 的参考实现是 “CPython”,这很可能就是你正在使用的 Python。CPython 是用 C 语言编写的,与 Python 不同,C 语言没有异常处理。所以,在 C 语言中,当你设计一个函数,并且想要表示”发生了错误”时,必须通过返回值来表示这个错误。
CPython 中的 hash() 函数可能返回错误,所以它定义返回值 -1 表示”发生了错误”。但如果哈希计算正确,而对象的实际哈希值恰好是 -1,这可能会造成混淆。所以约定是:如果哈希计算成功,并得到值是 -1,就返回 -2。
在 CPython 中,整数(“长整型对象”)的哈希函数中有专门的代码来处理这种情况:
https://github.com/python/cpython/blob/main/Objects/longobject.c#L2967