Python的类与对象体系是其面向对象特性的核心,理解其底层内存结构和对象模型,能帮助开发者更清晰地掌握属性查找、方法调用等行为的本质,避免开发中常见的认知误区。

Python对象模型的核心基础
Python中一切皆对象,无论是整数、字符串、函数还是类本身,都是对象。每个对象在内存中都包含三个核心部分:对象头、类型指针、对象体。对象头存储对象的引用计数和垃圾回收相关标记,类型指针指向该对象所属的类型对象,对象体存储对象的具体数据。
类本身也是对象,属于type类型的实例,而实例对象则是对应类对象的实例。这种层级关系构成了Python对象模型的基础。
类对象的内存结构
当定义一个类时,Python解释器会创建一个类对象,该类对象的内存中主要存储以下内容:
- 类的属性字典
__dict__,存储所有类属性、方法定义 - 类的基类信息
__bases__ - 类的名称、模块等元信息
- 指向
type的类型指针
实例对象的内存结构
类实例化时创建的对象,内存中主要存储:
- 实例的属性字典
__dict__,仅存储实例独有的属性 - 指向所属类对象的类型指针
__class__ - 对象头的相关信息
内存结构的代码验证
我们可以通过简单的代码观察类和实例的内存相关特性,以下示例基于CPython实现。
class Student:
# 类属性
school = "默认学校"
def __init__(self, name):
# 实例属性
self.name = name
def study(self):
print(f"{self.name}正在学习")
# 创建两个实例对象
stu1 = Student("张三")
stu2 = Student("李四")
# 查看类对象的__dict__,包含类属性和方法
print("类Student的__dict__键:", list(Student.__dict__.keys()))
# 输出:['__module__', 'school', '__init__', 'study', '__dict__', '__weakref__', '__doc__']
# 查看实例对象的__dict__,仅包含实例属性
print("stu1的__dict__:", stu1.__dict__) # {'name': '张三'}
print("stu2的__dict__:", stu2.__dict__) # {'name': '李四'}
# 验证类属性和实例属性的存储差异
# 修改类属性,所有实例都会受影响
Student.school = "新学校"
print(stu1.school) # 新学校
print(stu2.school) # 新学校
# 给stu1添加实例独有的属性
stu1.age = 18
print("添加age后stu1的__dict__:", stu1.__dict__) # {'name': '张三', 'age': 18}
# stu2没有age属性
print("stu2的__dict__:", stu2.__dict__) # {'name': '李四'}
属性查找的内存逻辑
当访问一个对象的属性时,Python的查找顺序是先从实例对象的__dict__中查找,如果找不到,再到类对象的__dict__中查找,如果类有继承关系,还会按照继承链向上查找。
我们可以通过自定义__getattribute__方法观察这个查找过程:
class CustomClass:
class_attr = "类属性值"
def __init__(self):
self.instance_attr = "实例属性值"
def __getattribute__(self, item):
print(f"尝试访问属性:{item}")
# 调用父类的__getattribute__完成默认查找逻辑
return super().__getattribute__(item)
obj = CustomClass()
print(obj.instance_attr)
# 输出:
# 尝试访问属性:instance_attr
# 实例属性值
print(obj.class_attr)
# 输出:
# 尝试访问属性:class_attr
# 类属性值
方法的内存存储与调用
类中定义的方法,默认存储在类对象的__dict__中,实例对象本身不存储方法。当实例调用方法时,Python会将实例作为第一个参数self传递给方法,这个过程是通过描述符协议实现的。
以下代码可以验证方法的存储位置:
class Demo:
def func(self):
pass
# 方法存储在类的__dict__中
print("func是否在Demo的__dict__中:", "func" in Demo.__dict__) # True
# 实例的__dict__中没有func
d = Demo()
print("func是否在d的__dict__中:", "func" in d.__dict__) # False
# 查看方法的类型
print(type(Demo.func)) # <class 'function'>
print(type(d.func)) # <class 'method'>
可以看到,类中的函数未绑定实例时类型是function,当通过实例访问时,会被包装成method类型,自动传入实例作为self参数。
内存优化的相关特性
Python为了减少实例对象的内存开销,提供了__slots__属性。当类中定义__slots__时,实例对象不再有__dict__,而是按照__slots__定义的属性列表分配固定大小的内存,仅存储指定属性。
class SlotStudent:
__slots__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
s = SlotStudent("王五", 20)
print("实例是否有__dict__:", hasattr(s, "__dict__")) # False
# 无法动态添加__slots__之外的属性
try:
s.grade = 3
except AttributeError as e:
print("添加属性失败:", e) # 'SlotStudent' object has no attribute 'grade'
这种方式适合需要创建大量实例的场景,能有效降低内存占用,但代价是失去了动态添加属性的灵活性。
总结
Python的类与对象内存结构遵循一切皆对象的设计理念,类对象存储类级别的属性和方法,实例对象仅存储自身独有的属性,通过类型指针关联所属类。属性查找遵循实例到类再到继承链的顺序,方法通过描述符协议实现实例绑定。理解这些底层逻辑,能帮助开发者更合理地设计类结构,在需要时选择合适的内存优化方案。