Python源码解读之五 对象的多态性和行为
对象的多态性
Python创建一个对象,比如PyFloatObject,会分配内存并进行初始化。然后内部统一使用泛型指针 PyObject* 来保存和维护这个对象,而不是PyFloatObject *。而不是PyFloatObject *。通过PyObject *保存和维护对象,可以实现更加抽象的上层逻辑,而不用关心对象的实际类型和实现细节。
Py_hash_t
PyObject_Hash(PyObject *V);
该函数可以计算任意对象的哈希值,而不用关心对象的类型是啥,它们都可以使用这个函数。
但是不同的类型对象,行为也是千差万别,哈希值的计算方式也是如此,那PyObject_Hash函数是如何解决这个问题的呢?不用想,因为元信息存储在对应的类型对象中,所以肯定会通过其ob_type拿到指向的类型对象。而类型对象中有一个成员叫tp_hash,它是一个函数指针,指向的函数专门用来计算其实例对象的哈希值。所以我们看一下PyObject_Hash的函数定义,看看它内部都做了什么,该函数位于Object/Object.c中。
Py_hash_t
PyObject_Hash(PyObject *v)
{
/* Py_TYPE是一个宏,用来获取PyObject *内部的ob_type */
PyTypeObject *tp = Py_TYPE(v);
/* 获取对应的类型对象内部的tp_hash */
/* tp_hash是一个函数指针,对应__hash__ */
if (tp -> tp_hash != NULL)
/* 如果tp_hash不为空,证明确实指向了具体的hash函数
* 那么拿到函数指针之后,通过*获取对应的函数
* 然后将PyObject *传进去计算哈希值,返回
*/
return (*tp -> tp_hash)(v);
/* 走到这里说明tp_hash为空,但这存在两种可能
* 1. 该类型对象可能还未完全初始化, 导致tp_hash暂时为空
* 2. 该类型本身就不支持其 "实例对象" 被哈希
*/
/* 如果是第一种情况,那么它的tp_dict(属性字典)一定为空
* tp_dict是动态设置的,它为空,是类型对象没有完全是初始化的重要特征
* 但如果tp_dict不为空,说明类型对象一定已经被完全初始化了
* 所以此时tp_hash要是还为空,就真的说明该类型不支持实例对象被哈希
*/
if (tp->tp_dict == NULL) {
/* 属性字典为空,那么先进行类型的初始化 */
if (PyType_Ready(tp) < 0)
return -1
/* 然后再看是否tp_hash是否为空,为空的话,说明不支持哈希
* 不为空则调用对应的哈希函数
*/
if (tp->tp_hash != NULL)
return (*tp->tp_hash)(v);