printf 源码_Python源码学习笔记(一)初识PyObject

c66e61b36c97c6be8b569b0cc1c57121.png

Python里有一句广为流传的话叫“Everything is object, except keywords”。就如同字面意思,抛去诸如if else之类的关键字,其余的东西皆是对象。我也一直比较好奇Python的对象是怎么样存在于内存的,于是就查看相关书籍和源码,在此做一下笔记。

PyObject

// file:object.h

/* PyObject_HEAD defines the initial segment of every PyObject. */
/* HEAD_EXTRA是Python Debug 模式下使用的,不影响我们理解 */
/* ob_refcnt 是Python耳熟能详的引用计数 */
/* ob_type指针指向一个 类型 变量,这个变量表明了当前Object在Python里是什么类型 */
#define PyObject_HEAD                   
    _PyObject_HEAD_EXTRA                
    Py_ssize_t ob_refcnt;               
    struct _typeobject *ob_type;

/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 */

typedef struct _object {
    PyObject_HEAD
} PyObject;

从上方官方注释理解一下,就是说这个PyObject是在C实现中所有Object的基类(不过基类这种解释并不准确,这是纯C语言)。要注意的是,它并不是我们在python中的object类,因此,我们在Python中是找不到与之对应的对象的。

英文注释中写了很重要的一句:This is inheritance built by hand。就是说其他Python对象是可以继承这个PyObject的,但是这又是C,因此需要Python开发者手动操作。

那Python源码是如何实现类继承的效果呢?我们写一段C看看。

struct A {
	int a = 1;
	int b = 1;
};

struct B
{
	int a = 2;
	int b = 2;
	int c = 1;
};

int main()
{
	A a;
	B b;
	A* pa = &a;
	B* pb = &b;
	pa = (A*)pb;
	printf("a=%d, b=%d", pa->a, pa->b);
}

/*
  output:a=2, b=2
*/

我把声明为B类型的指针强制转换成A类型的指针,这时候就可以用A的指针去访问B结构体了,这就跟B继承了A似的。

从底层来说,A类型指针不过是告诉编译器pa->a要从这个结构体所在内存的第1~4个字节(32位)去读数据而已。即使B结构体比A结构体本身多出一个int,也不妨碍。

但是,这样的操作的安全性需要完全由开发者自己去保证。比如下面这段代码就会出现意想不到的结果(我把结构体B中a的类型改成了float)。

struct A {
	int a = 1;
	int b = 1;
};

struct B
{
	float a = 2.0;
	int b = 2;
	int c = 1;
};

int main()
{
	A a;
	B b;
	A* pa = &a;
	B* pb = &b;
	pa = (A*)pb;
	printf("a=%d, b=%d", pa->a, pa->b);
}
/*
  output:a=1073741824, b=2
*/

再多说一句,这种方式是不支持隐式类型转换的。比如下面这样。

int main()
{
	A a;
	B b;
	A* pa = &a;
	B* pb = &b;
	pa = pb;    // 这里不能通过编译
}

那么函数呢?如何实现类似C++的多态呢?Python源码中,并不会直接在结构体中定义一个函数,而是声明一个函数指针,函数的定义在外部,通过函数指针来实现多态,这就很C++了。比如下面这段代码。

#include <stdio.h>


void FuncA() {
	printf("AAA");
}

void FuncB() {
	printf("BBB");
}

struct A {
	int a = 1;
	int b = 1;
	void (*p_func)(void) = &FuncA;
};

struct B
{
	int a = 2.0;
	int b = 2;
	void(*p_func)(void) = &FuncB;

	int c = 1;
};

int main()
{
	A a;
	B b;
	A* pa = &a;
	B* pb = &b;
	pa = (A*)pb;
	pa->p_func();
}

/*
  output:BBB
*/

回到PyObject。有了Python内部实现关于继承与多态的理解之后,PyObject的作用就差不多了,毕竟他所包含的信息非常之少,一个引用计数和一个类型指针,而类型指针,则是一个相当复杂的结构,下一篇再详细研究一波。

PyVarObject

除了PyObject,翻看object.h,还可以看到一个PyVarObject。它的定义如下:

//file:object.h

/*
  无非就是比普通的PyObject多一个ob_size。
  Py_ssize_t是一个Python定义的宏,它在不同环境下
  会是不同的内容。按其语义来说就是ob_size是专门用来记数的。
  不深究就理解为unsigned int就好,深究就展开宏研究研究。
*/
#define PyObject_VAR_HEAD               
    PyObject_HEAD                       
    Py_ssize_t ob_size; /* Number of items in variable part */

typedef struct {
    PyObject_VAR_HEAD
} PyVarObject;

PyVarObject是Python中可变长度的对象的基类,比如python中的list。而PyObject则是定长对象的基类,比如python中的int。

细读PyVarObject的定义可以发现,按照上面记录的手动继承逻辑,PyVarObject就是PyObject的子类。