Python
是一种非常灵活的编程语言,它的灵活性很大程度上来自于它的对象系统。
在Python
中,一切都是对象,这意味着无论是数字、字符串,还是我们自己定义的类的实例,它们在底层都遵循相同的规则。
本文尝试揭开Python
的对象系统的神秘面纱。
1. 对象和类型
在Python
中,每个对象都有一个类型(type
),而类型本身也是一个对象。
例如,int
是整数的类型,str
是字符串的类型,而我们自己定义的类(class
)也是一种类型。
1.1. 对象 PyObject
在CPython
中,所有对象都基于一个底层的结构体PyObject
。
这个结构体定义了每个对象的基本属性,比如它的引用计数(用于垃圾回收)和它的类型指针。
PyObject
的定义在源码中:Include/object.h
中。
// _object 就是 PyObject 的别名
typedef struct _object { };
当我们创建一个整数对象时,它实际上是一个PyLongObject
,它扩展了PyObject
结构体:
// _longobject 是 PyLongObject 的别名
struct _longobject {PyObject_HEAD_PyLongValue long_value;
};
_longobject
定义在源码文件:Include/cpython/longintrepr.h
中。
Python
代码中,我们可以通过type()
函数查看一个对象的类型:
a = 42
print(type(a)) # <class 'int'>s = "hello"
print(type(s)) # <class 'str'>
1.2. 类型 PyTypeObject
类型(PyTypeObject
)定义了对象的行为。
它的定义在源码:Doc/includes/typestruct.h
文件中。
它的定义中包含了许多函数指针,这些函数指针决定了对象如何响应某些操作。
例如,tp_str
函数指针定义了对象如何转换为字符串,
tp_as_number
函数指针中则定义了加减乘除等等数值计算操作。
typedef struct _typeobject {PyObject_VAR_HEADconst char *tp_name; /* For printing, in format "<module>.<name>" *//* Method suites for standard classes */PyNumberMethods *tp_as_number;reprfunc tp_str; // 省略... ...
} PyTypeObject;
其中的PyNumberMethods
在Include/cpython/object.h
中有定义。
包含了各种数值计算的函数指针。
typedef struct {/* Number implementations must check *both*arguments for proper type and implement the necessary conversionsin the slot functions themselves. */binaryfunc nb_add;binaryfunc nb_subtract;binaryfunc nb_multiply;// 省略... ...} PyNumberMethods;
也就是说,当一个类实现了这些函数指针,那么它的对象就能执行这些操作。
2. 类的特殊方法
在Python
中,我们经常使用特殊方法(如 __add__
、 __str__
等)来定义对象的行为。
实际上,这些特殊方法与上面介绍的函数指针是相互关联的。
比如,类的__add__
方法与上一节中PyTypeObject
中的PyNumberMethods
中的nb_add
相对应,
而__str__
则与上面的tp_str
相对应。
只要类实现了这些特殊方法(比如,__add__
和__str__
),那么,PyTypeObject
中对应的函数指针就指向这些特殊方法的实现。
根据这个类创建的对象就有了相应的功能。
下面通过简单的例子来看看这个函数指针的作用。
首先,我们创建一个类,先不实现__add__
和__str__
。
class MyClass:def __init__(self, n):self.n = n
然后根据这个类创建一个对象,并进行加法运算:
if __name__ == "__main__":obj = MyClass(10)obj + 1
运行结果:
果然,因为PyTypeObject
中PyNumberMethods
字段中的nb_add
对应的函数指针没有实现,无法进行加法运算。
修改MyClass类,实现__add__
方法。
class MyClass:def __init__(self, n):self.n = ndef __add__(self, m):self.n += mif __name__ == "__main__":obj = MyClass(10)obj + 1print(obj)
这时,MyClass
的对象可以正常进行加法运算了,但是print
出来的结果是对象的内存地址,我们希望看到对象中n
的值。
再次修改MyClass
,实现__str__
方法,也就是对应PyTypeObject
中tp_str
对应的函数指针。
class MyClass:def __init__(self, n):self.n = ndef __add__(self, m):self.n += mdef __str__(self):return f"当前值: {self.n}"
这样,MyClass
的对象既可以进行加法运算,也可以输出可读性较好的内容。
再补充一点,子类继承父类时,会自动继承父类中已经实现的特殊方法,比如:
class MyInt(int):passa = MyInt(10)
b = MyInt(20)
print(a + b) # 输出: 30
MyInt
继承了int
类型的nb_add
和tp_str
。
3. 类型的创建
在Python
中,我们可以通过两种方式创建类型:静态定义和动态分配。
3.1. 静态定义
Python
的内置类型(如int
、float
)是通过静态定义的方式创建的。
它们的特殊方法直接指向具体的实现函数。
int
和float
定义的源码分别在:Include/longobject.h
和Include/floatobject.h
。
3.2. 动态分配
我们自己定义的类是通过动态分配的方式创建的。
当我们使用class
语句定义一个类时,Python
会自动为我们设置好所有必要的特殊方法。
例如:
class MyClass:def __str__(self):return "Hello from __str__!"obj = MyClass()
print(obj) # 输出: Hello from __str__!
在这个例子中,MyClass
的tp_str
函数指针被设置为一个函数,这个函数会调用我们的 _str__
方法。
4. self和方法
在Python
类的定义中,方法是一种特殊的属性。
当我们通过实例调用方法时,Python
会自动将实例作为第一个参数传递给方法。
class MyClass:def my_method(self):return "Hello from my_method!"obj = MyClass()
print(obj.my_method()) # 输出: Hello from my_method!
上面的例子中,my_method
是一个函数,但它通过描述符协议变成了一个方法。
当我们通过obj.my_method()
调用它时,self
参数会自动被设置为obj
。
这里面提到的描述符协议是Python
中一个非常强大的机制,它允许对象控制对属性的访问(如获取、设置和删除)。
描述符是Python
中实现属性访问控制的基础,也是许多高级功能(如property
、classmethod
、staticmethod
等)的底层实现原理。
描述符本质上就是一个包含以下方法的类:
__get__(self, instance, owner)
:用于访问属性时调用,instance
是访问属性的实例,owner
是该实例所属的类__set__(self, instance, value)
:用于设置属性时调用__delete__(self, instance)
:用于删除属性时调用
根据描述符协议,当通过对象(obj
)访问my_method
时,返回的是一个绑定方法,它会自动将实例对象作为self
参数,这种行为是由__get__
方法实现的。
5. 总结
Python
对象系统的核心是PyObject
和PyTypeObject
。
每个对象都有一个类型,而类型定义了对象的行为,特殊方法与CPython
中的函数指针之间存在直接的映射关系。
Python
会根据我们定义的特殊方法自动设置函数指针,同时,特殊方法也可以从父类继承。
通过理解这些概念,我们可以更好地理解Python
的动态特性和灵活性。当我们使用class
定义一个类时,可以想想背后发生的故事。