Python 提供了完整的面向对象编程能力,将面向对象编程思想带到实际项目,可极大提高开发效率。因此,面向对象编程也是 Python 面试中必问的重要话题。
想要在 Python 项目中应用面向对象编程技术,除了掌握基本的理论概念外,还要理解 Python 对象模型、 类机制、继承与属性查找的关系、描述符以及元类等诸多知识。这些都是面试中经常考察的关键知识点。
本节精选若干典型面试题,以此抛砖引玉。
❓ 如何理解面向对象编程中的方法重写( overriding )和重载( overloading )?
请结合 Python 或其他编程语言进行说明。
方法重写 ( overriding )是指在子类中重新实现已在父类中定义的方法。
在面向对象编程语言中,子类可以继承父类中的方法,而无须重新编写相同的方法。但有时子类并不想原封不动地继承父类所有功能,想对父类中的某些方法进行修改,这就需要采用方法重写特性。方法重写不能发生在同一个类中,只能发生在子类中。
这是一个最简单的方法重写实例:
class Dog:
def eat(self):
print('yummy!')
def yelp(self):
print('woof!')
class Sleuth(Dog):
def yelp(self):
print('WOOF! WOOF! WOOF!')
Dog 是一个普通狗类,实现了 eat 和 yelp 方法。猎犬 Sleuth 继承于 Dog 类,因此继承了父类的 eat 方法。注意到,我们对 yelp 方法进行 重写 ,以连续三个大写的 WOOF 突出猎犬铿锵有力的吠声。
这样一来,Sleuth 类继承了 Dog 中的 eat 方法,但自己的 yelp 方法覆盖了 Dog 中的相关定义:
>>> sleuth = Sleuth()
>>> sleuth.eat()
yummy!
>>> sleuth.yelp()
WOOF! WOOF! WOOF!
重载 ( overloading )既可发生在同一个类的方法之间,一般称作方法重载;亦可发生在普通函数间,一般称作函数重载。这个特性允许开发人员定义名字相同,但输入参数不同的类方法或者普通函数,即同名方法/函数的不同版本。
当程序调用重载方法或函数时,编译器将根据 参数个数 及 参数类型 ,自动绑定正确的版本。由于方法/函数绑定时涉及类型检查,因此一般只有静态类型编程语言才支持重载特性。
Python 是一种动态类型编程语言,不支持方法重载,我们举一个简单的 C++ 程序作为例子:
#include <iostream>
using namespace std;
void print(int i) {
cout << " Here is int " << i << endl;
}
void print(double f) {
cout << " Here is float " << f << endl;
}
void print(char const *c) {
cout << " Here is char* " << c << endl;
}
int main() {
print(10);
print(10.10);
print("ten");
return 0;
}
程序定义了 print 函数,分为 3 个不同版本,分别以整型、双精度浮点以及常字符串为参数。main 函数中调用 print 函数时,编译器将根据参数类型,自动选择正确的 print 函数版本。以 print(10) 为例,由于参数 10 是一个整数,编译器可以据此推导出 void print(int i) 版本。
❓ Python 支持多继承吗?试说明多继承场景下实例对象类属性查找顺序?
Python 支持多继承,只需将基类按顺序逐一声明即可:
class Child(Base1, Base2):
pass
当子类实例对象查找某个类属性时,先在子类 Child 中查找,再按定义顺序到基类中逐个中查找。如果基类也继承于其他类的基类,Python 将沿着继承链逐级回溯,最终来到 object 。
类属性查找顺序决定程序的行为,不可不察,特别是在复杂多继承场景下。实际上,Python 类属性查找顺序是一个特殊的拓扑排序。这个拓扑排序首先是深度优先的,其次需要确保多继承基类按照定义的顺序查找。
接下来,我们构造一个多继承关系网,考察决定属性查找顺序的重要因素,例子如下:
class A: pass
class B: pass
class C(A): pass
class D(C): pass
class E(B, A): pass
class F(D, E): pass
例子涉及各个类的继承关系图如下:

子类总比父类先被搜索,因此必须满足以下关系,拓扑排序即可胜任:
F 先于 D ; F 先于 E ; D 先于 C ; C 先于 A ; A 先于 object ; E 先于 A ; E 先于 B ; B 先于 object ;
而根据多继承基类列表顺序,必须保证:
B 先于 A ; D 先于 E ;
因此,F 最先被搜索,接着是 D ,然后按照深度优先的原则来到 C ;由于 B 先于 A ,不能接着搜索 A ;这时只能先搜索 E 分支,然后是 B ,再到 A ;A 和 B 皆搜索过后才能搜索 object 。因此,完整的搜索顺序是这样的:
F -> D -> C -> E -> B -> A -> object
Python 完成类对象初始化后,通过 C3 算法计算类属性搜索顺序,并将其保存在 __mro__ 属性中。我们可以据此确认推理结果:
>>> F.__mro__
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
如果规则前后矛盾,Python 将抛 TypeError 异常。这是一个典型的例子:
class A: pass
class B: pass
class C(A, B): pass
class D(B, A): pass
class F(C, D): pass
对于 F 类,基类 C 要求 A 先于 B 被搜索,而基类 D 要求 B 先于 A 被搜索,前后矛盾:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B
由于多继承存在一定的歧义性,实际项目开发一般不鼓励复杂的多继承关系。如果多继承不可避免,则需要严谨确认类属性搜索顺序。最好查看 __mro__ 属性确认顺序符合预期,切勿想当然。
更多面试真题
❓ 试设计装饰器 mystaticmethod ,实现与 staticmethod 相同的功能
❓ Python 如何实现单例模式?试举例说明。
❓ 如果程序中有成千上万的 User 类实例对象,如何优化内存使用?
❓ 用 type 元类型创建一个等价的 MyFloat 类
上述的问题,你是否都能对答如流呢?点击 “阅读原文”,获取详细题解!
点击公众号名片,关注小菜学编程
跟着小菜一起成长吧!👇
😘 原创不易,求赞,求在看




