六、面向对象(P53–P60)
P53 · 知识点:构造方法
【题目】Python 类中用于初始化实例的方法是? A. init B. __construct C. init D. constructor
答案:A
【考点】init 实例初始化方法(构造后自动调用)。
【结论】选 A。Python 类中用于初始化实例的魔法方法是 init,在对象创建完成后自动调用以设置实例属性。
【逐项辨析】
- A. init:正确。init(self, ...) 是 Python 的实例初始化方法,在 new 创建对象后自动调用,用于绑定实例属性。调用类名(...)时,解释器自动触发 new 和 init。
- B. __construct:错误。这是 PHP 的构造方法命名,Python 不使用此名称。
- C. init:错误。缺少双下划线的普通名称不是魔法方法,不会被解释器自动识别和调用。
- D. constructor:错误。这是 Java、C++ 等语言的构造方法通用术语,Python 中对应概念由 new(创建)和 init(初始化)共同完成。
【知识点】在 Python 的对象创建流程中,new(cls, ...) 负责实际分配内存并返回实例(真正的“构造”),init(self, ...) 负责初始化实例状态。new 是类方法(第一个参数是 cls),而 init 是实例方法(第一个参数是 self)。绝大多数类只需重写 init,只有涉及不可变子类(如 int、tuple 子类)或元类编程时才需要重写 new。init 不能返回非 None 值,否则会触发 TypeError。双下划线方法(dunder methods)是 Python 数据模型的核心,使得自定义对象可以响应内置函数和运算符。
【记忆锚点】“双下划线 init,构造完毕就执行;没有双下不魔法,PHP 用 construct 要区分。”
【易混对比】
| 概念 | 方法名 | 调用时机 | 是否必须重写 | 返回值限制 |
|---|---|---|---|---|
| 创建对象 | new(cls) | 实例化第一步 | 极少 | 必须返回实例 |
| 初始化对象 | init(self) | new 之后自动调用 | 经常 | 必须为 None |
| PHP 构造 | __construct() | new 时调用 | 按需 | 无特殊限制 |
换问法:若问“Python 中真正负责创建并返回实例对象的方法是?”,则答案变为 new。
【自测】
以下代码输出什么?
class Demo:
def __new__(cls):
print("new")
return super().__new__(cls)
def __init__(self):
print("init")
d = Demo()答:先输出 "new",再输出 "init"。因为实例化时先调用 new 创建对象,再调用 init 初始化。与第 P56 题连考。
【知识关联】
- 同库关联:与 P54(self)、P58(魔法方法);
__init__是初始化不是分配。 - 实现层:
__new__分配,__init__初始化;默认 object.new。 - 面试追问:①
__init__能否返回值?② 与 Java 构造器差异?
【拓展延伸】
- 变式问法:继承时是否自动调父类 init(否,需 super)。
- 版本差异:语义稳定。
- 工程注意点:复杂对象可用 dataclass 生成。
P54 · 知识点:self
【题目】实例方法的第一个参数按约定命名为? A. this B. self C. me D. cls
答案:B
【考点】self 与 cls 的命名约定。
【结论】选 B。Python 实例方法的第一个位置参数按约定命名为 self,代表调用该方法的实例对象本身。
【逐项辨析】
- A. this:错误。this 是 Java、C++、JavaScript 等语言中表示当前实例的关键字;Python 中没有 this 关键字,使用 self 作为约定名称。
- B. self:正确。这是 Python 社区强烈约定的命名规范,虽然 self 不是保留字,但几乎所有代码都遵循此约定,否则可读性极差。
- C. me:错误。某些语言(如 VB)使用 me,Python 中无此约定,若用 me 会被视为不符合规范的参数名。
- D. cls:错误。cls 是类方法(@classmethod)的第一个参数约定名称,代表类本身而非实例。
【知识点】Python 的方法调用机制是显式传递实例/类对象:当调用 obj.method(arg) 时,解释器实际转换为 MyClass.method(obj, arg)。因此实例方法必须定义至少一个位置参数来接收该实例引用,这个参数按约定叫 self。同理,@classmethod 装饰的方法调用 Cls.method(arg) 实际转换为 MyClass.method(Cls, arg),第一个参数约定叫 cls。self 和 cls 都不是关键字,理论上可以改名,但强烈不建议。如果忘记写 self,调用实例方法时会报 "takes 0 positional arguments but 1 was given" 的错误,因为解释器自动传入了实例却找不到形参接收。
【记忆锚点】“实例 self,类用 cls;this 是 Java,me 是 VB;约定不遵守,排错找半天。”
【易混对比】
| 方法类型 | 第一个参数约定 | 装饰器 | 调用方式 | 参数代表 |
|---|---|---|---|---|
| 实例方法 | self | 无 | obj.method() | 实例对象 |
| 类方法 | cls | @classmethod | Cls.method() 或 obj.method() | 类对象 |
| 静态方法 | 无特殊要求 | @staticmethod | 任意方式调用 | 无自动传入参数 |
换问法:若问“类方法的第一个参数按约定应命名为?”,则答案变为 cls。
【自测】
以下代码会报错吗?为什么?
class A:
def show():
print("hi")
a = A()
a.show()答:会报错 TypeError: A.show() takes 0 positional arguments but 1 was given。因为 a.show() 会隐式把 a 作为第一个参数传入,但 show() 没有定义 self 参数来接收。与第 P53 题连考。
【知识关联】
- 同库关联:与 P53/P56;self 是约定名不是关键字。
- 实现层:方法定义时第一个参数,调用时自动传入实例。
- 面试追问:① 能否不叫 self?② 类方法/静态方法差异?
【拓展延伸】
- 变式问法:
@staticmethod无 self;@classmethod传 cls。 - 版本差异:语义稳定。
- 工程注意点:始终写 self 提高可读性;不要手动传。
P55 · 知识点:继承
【题目】class B(A): 表示?
A. A 继承 B B. B 继承 A C. B 实现 A 接口 D. 组合关系
答案:B
【考点】类定义括号中列出父类。
【结论】选 B。class B(A): 表示 B 继承自 A,B 是子类(派生类),A 是父类(基类)。
【逐项辨析】
- A. A 继承 B:错误。继承方向颠倒,括号外的是子类,括号内的是父类。
- B. B 继承 A:正确。B 获得 A 的所有非私有属性和方法,可以重写或扩展。
- C. B 实现 A 接口:错误。Python 中没有 interface 关键字,类定义括号内是继承关系而非接口实现关系(虽然抽象基类 ABC 可以模拟接口概念)。
- D. 组合关系:错误。组合通常表现为一个类的实例作为另一个类的属性(如 class B: def init(self): self.a = A()),而非继承语法。
【知识点】Python 支持多重继承,语法为 class C(A, B):,方法解析顺序(MRO)采用 C3 线性化算法,可通过 ClassName.mro 或 mro() 查看。子类可以重写父类方法,通过 super() 调用被重写的父类方法。Python 3 中所有类默认继承自 object(新式类)。继承是“is-a”关系,组合是“has-a”关系,设计时应优先使用组合以保持低耦合。检查继承关系使用内置函数 issubclass(B, A) 和 isinstance(obj, A)。
【记忆锚点】“括号外面是儿子,括号里面是爸爸;B 把 A 括起来,B 继承 A 没毛病。”
【易混对比】
| 关系类型 | 语法表现 | 语义 | 设计原则 |
|---|---|---|---|
| 继承 | class B(A) | is-a 关系 | 强耦合,慎用深继承链 |
| 组合 | self.a = A() | has-a 关系 | 低耦合,优先推荐 |
| 实现接口 | class B(A)(A 是 ABC) | 必须实现抽象方法 | 用 @abstractmethod 约束 |
| 依赖 | 方法参数传入 | 临时使用 | 最弱耦合 |
换问法:若问“Python 中 class C(A, B) 的方法按什么顺序查找?”,则答案涉及 MRO/C3 线性化。
【自测】
以下代码的输出顺序是什么?
class A:
def f(self): print("A")
class B(A):
def f(self): print("B")
class C(A):
def f(self): print("C")
class D(B, C):
pass
D().f()答:输出 "B"。按 MRO(D→B→C→A→object)查找,B 在 C 前面先找到。与第 P56 题连考。
【知识关联】
- 同库关联:与 P56(super)、P57(多态)、J13/J12 对照。
- 实现层:MRO C3 线性化;多继承方法解析顺序。
- 面试追问:① MRO 是什么?② 菱形继承?
【拓展延伸】
- 变式问法:
class C(A,B)查找顺序。 - 版本差异:Py3 统一新式类与 MRO。
- 工程注意点:避免复杂多继承;优先组合。
P56 · 知识点:super
【题目】子类的 init 中调用父类初始化方法,正确写法是? A. 父类名().init() B. parent.init() C. super().init() D. super.init()
答案:C
【考点】super() 调用父类方法(Python 3 语法)。
【结论】选 C。在 Python 3 中,子类通过 super().init() 调用父类的初始化方法,super() 返回一个代理对象用于委托方法调用。
【逐项辨析】
- C. super().init():正确。Python 3 的零参数 super() 会自动根据当前类和实例推断出正确的父类和方法解析顺序,简洁且支持多重继承。
- B. parent.init():错误。parent 不是内置关键字,且 init 缺少双下划线,完全不是合法的父类调用方式。
- A. 父类名().init():错误。首先父类名()会创建一个父类的新实例而非操作子类实例,其次 init 不是 init,且即使写成 Parent.init(self) 虽可行但不推荐(硬编码类名、不支持多继承协作)。
- D. super.init():错误。super 是内置函数/类,必须加括号调用 super() 才能返回代理对象,super.init 只是访问 super 这个类的 init 属性,语法完全错误。
【知识点】super() 是 Python 实现合作式多重继承的关键工具。Python 3 中无参 super() 等价于 Python 2 中的 super(当前类名, self)。super() 不是简单地调用“直接父类”,而是遵循 MRO 顺序调用下一个类的方法,因此在复杂钻石继承中能保证每个类的方法只被调用一次。如果子类重写了 init,父类的 init 不会自动执行,必须显式调用 super().init() 来确保父类侧的属性正确初始化。在多重继承场景中,硬编码父类名(如 Parent.init(self))会破坏 super() 的协作链,应始终优先使用 super()。
【记忆锚点】“super() 带括号,再找 init;没括号是找属性,调用肯定报错误。”
【易混对比】
| 写法 | 语法正确性 | 是否推荐 | 支持多重继承 | 说明 |
|---|---|---|---|---|
| super().init() | 正确 | 强烈推荐 | 是 | Python 3 零参数,遵循 MRO |
| super(Child, self).init() | 正确 | Python 2 兼容用 | 是 | 冗长但语义明确 |
| Parent.init(self) | 正确 | 不推荐 | 否 | 硬编码类名,破坏协作链 |
| super.init() | 错误 | — | — | super 未实例化 |
换问法:若问“在钻石继承结构中,如何让所有父类的 init 都只执行一次?”,则答案应使用 super() 配合 MRO 链。
【自测】
补全以下代码,使 B 和 C 的初始化都能被 D正确调用:
class B:
def __init__(self):
print("B")
super().__init__()
class C:
def __init__(self):
print("C")
super().__init__()
class D(B, C):
def __init__(self):
print("D")
________ # 补全
D()答:补全
super().__init__()。输出顺序为 D、B、C(按 MRO:D→B→C→object)。与第 P55 题连考。
【知识关联】
- 同库关联:与 P55、J15(Java super)对照。
- 实现层:super() 基于 MRO 不是简单父类;Py3 无参 super。
- 面试追问:① 多继承中 super 调谁?② 与显式父类名?
【拓展延伸】
- 变式问法:菱形继承中 super 链。
- 版本差异:Py3 super() 更安全。
- 工程注意点:初始化链务必 super().init;协作多重继承。
P57 · 知识点:多态与鸭子类型
【题目】关于 Python 多态的说法,正确的是? A. 基于继承和“鸭子类型”,不强制要求显式接口,只要对象有对应方法即可调用 B. 必须实现接口才能实现多态 C. Python 不支持多态 D. 多态只适用于基本数据类型
答案:A
【考点】鸭子类型(duck typing)驱动的动态多态。
【结论】选 A。Python 的多态基于鸭子类型,只要对象实现了所需的方法或属性,就可以在不关心其具体类型的情况下使用。
【逐项辨析】
- A. 基于继承和“鸭子类型”,不强制要求显式接口,只要对象有对应方法即可调用:正确。Python 的动态类型机制使得多态不需要统一的基类或接口声明,运行时根据对象实际能力决定行为。
- B. 必须实现接口才能实现多态:错误。这是 Java、C# 等静态类型语言中接口多态的特征,Python 没有此强制要求。
- C. Python 不支持多态:错误。Python 不仅支持多态,而且其多态机制比静态语言更灵活。
- D. 多态只适用于基本数据类型:错误。多态在 Python 中广泛适用于所有自定义对象,例如 len() 可以对列表、字符串、字典、自定义对象等多种类型工作。
【知识点】多态(polymorphism)指同一接口在不同对象上产生不同行为。在 Java 中,多态通常通过继承父类或实现接口来实现编译时类型检查;而 Python 作为动态类型语言,采用鸭子类型(duck typing)——“如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子”。这意味着只要对象具有所需的方法/属性,就可以在任何期望该能力的地方使用,无需显式声明继承关系或实现接口。例如内置函数 len(obj) 会调用 obj.len(),只要对象实现了 len 方法即可。这种动态多态极大地提高了代码的灵活性和可扩展性,但代价是将部分类型错误推迟到运行时。Python 也支持通过抽象基类(abc.ABC 和 @abstractmethod)来模拟静态接口约束,但这是一种可选的编码规范而非语言强制。
【记忆锚点】“鸭子类型不问出处,会叫会走就是鸭;Java 先看户口本,Python 只看本事大。”
【易混对比】
| 特性 | 静态类型语言(Java/C#) | Python |
|---|---|---|
| 多态前提 | 继承同一父类或实现接口 | 具备所需方法/属性即可 |
| 类型检查时机 | 编译期 | 运行期 |
| 灵活性 | 较低,需预先定义契约 | 极高,任何对象只要“像”就行 |
| 错误暴露时机 | 编译时报错 | 运行时报错(若方法不存在) |
| 实现方式 | interface/abstract class | duck typing / ABC(可选) |
换问法:若问“Python 中以下哪个函数体现了鸭子类型的设计?”,可答 len()、iter()、next() 等内置函数。
【自测】
以下代码能正常运行吗?输出什么?
class Dog:
def speak(self): return "Woof"
class Cat:
def speak(self): return "Meow"
def animal_sound(a):
print(a.speak())
animal_sound(Dog())
animal_sound(Cat())答:能正常运行,输出 "Woof" 和 "Meow"。animal_sound 不检查参数类型,只依赖 speak 方法的存在,是典型的鸭子类型多态。与第 P58 题连考。
【知识关联】
- 同库关联:与 J13(Java 静态类型多态)对照——Python 鸭子类型。
- 实现层:协议/魔术方法;运行期才失败(AttributeError)。
- 面试追问:① 鸭子类型利弊?② 如何约束(typing.Protocol)?
【拓展延伸】
- 变式问法:可迭代协议 iter;上下文管理器。
- 版本差异:typing 体系完善。
- 工程注意点:文档写清协议;静态检查靠 mypy Protocol。
P58 · 知识点:魔法方法
【题目】让对象支持 len(obj) 调用,应实现?
A. len B. size C. str D. iter
答案:A
【考点】内置函数与魔法方法的映射关系。
【结论】选 A。要使自定义对象支持 len(obj) 调用,需要在类中实现 len(self) 方法并返回非负整数。
【逐项辨析】
- A. len:正确。len(self) 是 len() 内置函数对应的魔法方法,返回值有两条硬约束,而且违反后抛的异常类型不同(本机 3.12.3 实测):返回负整数 →
ValueError: __len__() should return >= 0;返回非整数(如 1.5)→TypeError: 'float' object cannot be interpreted as an integer。所以「否则引发 ValueError」只对「负数」这一半成立。 - B. size:错误。Python 中没有 size 这个标准魔法方法,len() 不会调用它。
- C. str:错误。str 对应 str(obj) 和 print() 的调用,用于返回面向用户的字符串表示。
- D. iter:错误。iter 对应 iter(obj) 和 for 循环的调用,用于返回迭代器对象,与长度计算无关。
【知识点】魔法方法(magic methods / dunder methods)是 Python 数据模型协议的核心,以双下划线开头和结尾,允许开发者自定义类以响应 Python 的内置语法和函数。常见映射关系:len() → len;str() → str;repr() → repr;iter() → iter;next() → next;obj[key] → getitem;obj[key] = value → setitem;with 语句 → enter / exit;运算符 + → add;运算符 == → eq;等等。实现 len 的对象被称为 Sized,是 collections.abc 中的一个抽象基类。注意 len 必须返回 int 且不能为负数,这是 Python 对序列/容器大小的硬性约束。
【记忆锚点】“len 找 len,str 找 str;iter 迭代器,size 没这回事。”
【易混对比】
| 内置调用 | 魔法方法 | 返回值类型 | 触发场景 |
|---|---|---|---|
| len(obj) | len | int(≥0) | 获取元素个数 |
| str(obj) | str | str | print()、str()、format() 等 |
| repr(obj) | repr | str | 交互式环境、repr() |
| iter(obj) | iter | iterator | for 循环、list() 等 |
| bool(obj) | bool | bool | 条件判断、any()、all() 等 |
| obj[k] | getitem | 任意 | 索引访问、切片 |
换问法:若问“让对象支持 print(obj) 输出自定义格式,应实现哪个魔法方法?”,则答案为 str(或 repr 作为兜底)。
【自测】
补全以下代码,使 len(b) 返回 3:
class Box:
def __init__(self, items):
self.items = items
def ________(self):
return len(self.items)
b = Box([1, 2, 3])
print(len(b))答:补全
__len__。当调用 len(b) 时,解释器自动转换为 b.len()。与第 P59 题连考。
【知识关联】
- 同库关联:与 P59(str/repr)、P53;双下划线方法。
- 实现层:特殊方法由解释器在语法层调用;
len→__len__等。 - 面试追问:① 常用魔术方法有哪些?②
__call__?
【拓展延伸】
- 变式问法:运算符重载 add;容器协议。
- 版本差异:持续新增(async 相关)。
- 工程注意点:不要发明同名双下划线;dataclass 自动生成部分。
P59 · 知识点:str 与 repr
【题目】关于 str 和 repr 的区别,正确的是?
A. 两者没有区别 B. str 面向用户(str()、print() 调用),repr 面向开发者(repr()、交互式环境显示调用) C. print() 调用 repr D. str 必须返回整数
答案:B
【考点】字符串表示的两个层级。
【结论】选 B。str 提供面向终端用户的可读字符串,repr 提供面向开发者的无歧义、尽可能可重建对象的字符串表示。
【逐项辨析】
- A. 两者没有区别:错误。两者设计目标完全不同,Python 在不同场景下优先调用不同方法。
- B. str 面向用户(str()、print() 调用),repr 面向开发者(repr()、交互式环境显示调用):正确。str 追求可读性,repr 追求准确性和可重建性。
- C. print() 调用 repr:错误。print(obj) 优先调用 str;仅在 str 未定义时才回退到 repr。
- D. str 必须返回整数:错误。str 和 repr 都必须返回字符串(str 类型),返回其他类型会触发 TypeError。
【知识点】repr 的规范要求返回一个尽可能准确的字符串,理想情况下 eval(repr(obj)) == obj(虽然并非所有对象都能做到)。当在交互式解释器中直接输入对象名或调用 repr(obj) 时,Python 调用 repr。str 则用于 str(obj)、print()、format()、f-string 的 {!s} 转换等场景,追求对用户友好。如果类只定义了 repr 而没有定义 str,则 str 会回退使用 repr 的实现;反之不会回退。根据官方最佳实践:repr 应当始终被定义,作为开发的“安全网”;str 在有更友好的用户描述时才额外定义。例如 datetime 对象的 repr 返回 datetime.datetime(2024, 1, 1, 0, 0),而 str 返回 '2024-01-01 00:00:00'。
【记忆锚点】“repr 是调试内参,str 是门面展示;repr 求准不求美,str 求美不求细。”
【易混对比】
| 维度 | repr | str |
|---|---|---|
| 目标受众 | 开发者 | 终端用户 |
| 追求目标 | 无歧义、可重建 | 可读性、友好性 |
| 触发场景 | repr()、交互式回显、%r | str()、print()、format()、%s |
| 兜底行为 | 无(最低层) | 若未定义则使用 repr |
| 最佳实践 | 必须实现 | 按需实现更友好的版本 |
| 示例输出 | Point(1, 2) | (1, 2) |
换问法:若问“在 Python 交互式解释器中直接输入变量名显示的内容由哪个方法控制?”,则答案为 repr。
【自测】
以下代码在交互式环境中输入 p 和 print(p) 分别输出什么?
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(1, 2)答:输入
p输出Point(1, 2)(调用 repr);print(p)输出(1, 2)(调用 str)。与第 P58 题连考。
【知识关联】
- 同库关联:与 P58;str() 用 str,repr() 用 repr。
- 实现层:调试用 repr,用户展示用 str;无 str 回落 repr。
- 面试追问:① 二者用途?② 容器元素打印用哪个?
【拓展延伸】
- 变式问法:
print(obj)vs 交互式回显。 - 版本差异:语义稳定。
- 工程注意点:日志用 repr 更可定位;f-string 默认 str,{!r} 用 repr。
P60 · 知识点:类变量与实例变量
【题目】输出结果是?
class A:
x = 1
a1 = A()
a2 = A()
a1.x = 2
print(a2.x)A. 1 B. 2 C. 抛异常 D. None
答案:A
【考点】类变量共享;实例赋值创建实例属性并遮蔽类变量。
【结论】选 A。a1.x = 2 创建的是 a1 的实例属性,不影响类变量,a2.x 仍读取类变量 A.x 的值为 1。
【推导过程】
class A: x = 1—— x 是类变量,存储在类对象 A 的 dict 中,所有实例共享。a1 = A(); a2 = A()—— a1 和 a2 被创建,此时两者都没有自己的实例属性 x,访问 a1.x 和 a2.x 都会通过属性查找链找到类变量 A.x,值为 1。a1.x = 2—— 这是对实例 a1 的属性赋值,Python 会直接给 a1 的实例字典 dict 添加键值对'x': 2,不会修改类变量 A.x。print(a2.x)—— a2 没有实例属性 x,继续向上查找类变量 A.x,仍为 1,因此输出 1。
【逐项辨析】
- A. 1:正确。a1.x = 2 仅给 a1 创建了实例属性,a2 仍访问类变量 A.x = 1。
- B. 2:错误。只有同时修改类变量(如 A.x = 2 或 a2.x = 2)才会让 a2.x 变为 2,a1.x = 2 不影响类变量。
- C. 抛异常:错误。a2.x 可以正常找到类变量,不会引发 AttributeError。
- D. None:错误。类变量 x 已初始化为 1,不会返回 None。
【知识点】Python 的属性查找遵循实例 → 类 → 父类的顺序。当通过实例访问属性时,先查实例 dict,再查类 dict;当通过实例赋值属性时,总是直接写入实例 dict(除非使用 slots 或描述符拦截)。类变量被所有实例共享,但如果某个实例对该名称进行了赋值操作,就会在该实例的 dict 中创建一个同名实例属性,从而“遮蔽”(shadow)类变量,使得后续通过该实例访问时优先看到实例属性而非类变量。修改类变量应通过类名赋值(A.x = 新值),这样所有未遮蔽的实例都会看到变化。可变类变量(如列表、字典)若通过实例方法修改其内容(如 append、update)会影响所有实例,但若重新赋值则会创建实例属性,这是常见的陷阱。
【记忆锚点】“实例赋值建新房,类变量还在老地方;要想大家一起变,A.x = 2 才有效。”
【易混对比】
| 操作 | 实际效果 | 影响范围 | 常见陷阱 |
|---|---|---|---|
A.x = 2 | 修改类变量 | 所有未遮蔽的实例 | 无 |
a1.x = 2 | 创建实例属性 | 仅 a1 | 误以为修改了类变量 |
a1.x.append(3)(x 是列表) | 修改可变类变量内容 | 所有实例 | 共享可变对象的副作用 |
del a1.x | 删除实例属性 | 仅 a1 | 删除后 a1.x 又暴露类变量 |
换问法:若问“以下代码输出什么?class A: x = []; a1 = A(); a2 = A(); a1.x.append(1); print(a2.x)”,则答案为 [1],因为列表是类变量且被原地修改。
【自测】
以下代码的输出是什么?
class A:
x = 1
a1 = A()
a2 = A()
A.x = 3
print(a1.x, a2.x)
a1.x = 5
A.x = 7
print(a1.x, a2.x)答:第一行输出
3 3(修改类变量,所有实例可见);第二行输出5 7(a1 已有实例属性 x,不受类变量修改影响;a2 仍访问类变量,变为 7)。与第 P54 题连考。
【知识关联】
- 同库关联:与 P69(引用)、J19(Java static)对照——Python 类变量共享。
- 实现层:类属性在类对象
__dict__;实例属性在实例__dict__;赋值实例名遮蔽。 - 面试追问:① 可变类变量共享陷阱?② 如何定义常量?
【拓展延伸】
- 变式问法:列表类变量被所有实例共享并修改。
- 版本差异:语义稳定。
- 工程注意点:可变默认用实例属性在 init 创建;常量全大写模块级。