Python 面向对象的核心概念有哪些?MRO 和描述符怎么理解?
Python 面向对象的核心是四件事:用类组织数据和行为的封装机制、通过继承复用代码、用多态让不同对象响应同一接口、以及 Python 自己的特殊之处——MRO、描述符、slots 这些面试高频考点。基础概念(类/实例/属性/方法)不展开,下面只说容易踩坑和被追问的部分。
追问
Python 的 MRO 是怎么排的?为什么不用深度优先?
Python 3 用 C3 线性化算法计算 MRO。核心规则:子类排在父类前面,同一层按定义顺序排,不能违反前两条规定。为什么不用深度优先?因为菱形继承下深度优先会重复访问基类。经典例子:D 继承 B 和 C,B 和 C 都继承 A,深度优先的顺序是 D→B→A→C→A,A 被访问两次。C3 的结果是 D→B→C→A,每个类只出现一次,且 B 在 C 前面(定义顺序)。通过 ClassName.__mro__ 可以查看任意类的解析顺序。
slots 能省多少内存?有什么代价?
普通 Python 对象用 __dict__ 存属性,一个空对象就要占 56 字节(64 位 CPython)。__slots__ 用固定数组替代字典,属性直接按偏移量访问,省掉哈希表开销。实际测量:100 万个只有 name 和 age 属性的对象,用 __dict__ 约 160MB,用 __slots__ 约 48MB,省 70%。代价是不能再动态添加属性,而且继承时如果父类没有声明 __slots__,子类照样会有 __dict__,优化白做。实际项目中,Django 的 QuerySet 用了 __slots__ 优化大量小对象。
描述符是什么?property 和 classmethod 跟它什么关系?
描述符是实现了 __get__、__set__、__delete__ 中任意一个的类。Python 的属性查找有个隐藏步骤:如果找到的对象是描述符,就调用它的 __get__ 返回结果,而不是直接返回对象本身。property 就是描述符——你的 getter/setter 被 __get__/__set__ 包装了;classmethod 也是描述符——它的 __get__ 把类传给函数而不是实例。区分数据描述符(有 __set__)和非数据描述符(只有 __get__):数据描述符优先级高于实例 __dict__,非数据描述符优先级低于实例 __dict__。这就是为什么 property 能拦截赋值而普通方法不行。
new 和 init 有什么区别?
__new__ 创建对象并返回,__init__ 初始化已创建的对象。__new__ 是类方法(第一个参数是 cls),__init__ 是实例方法(第一个参数是 self)。单例模式用 __new__ 控制:如果 _instance 已存在就直接返回,不再创建新对象。__init__ 做不到这点——它执行时对象已经创建了。另一个场景:不可变类型(str、int、tuple)的子类化必须重写 __new__,因为这些类型的对象在 __new__ 阶段就已经确定了值,__init__ 改不了。
写段代码
python# 描述符实现懒加载属性 class LazyProperty: def __init__(self, func): self.func = func def __get__(self, obj, cls): if obj is None: return self value = self.func(obj) obj.__dict__[self.func.__name__] = value # 缓存到实例字典 return value class Data: @LazyProperty def expensive(self): print("计算中...") return sum(range(1000000)) d = Data() print(d.expensive) # 计算中... 499999500000 print(d.expensive) # 499999500000(不再计算,从 __dict__ 直接取)