面向对象编程
类与对象、继承、多态、魔术方法与描述符。
知识点地图
- 知识类别:Python 面向对象编程的基础机制——类与实例、属性与方法(实例/类/静态)、继承与多态、魔术方法(运算符重载与对象行为定制)。
- 解决什么问题:把「数据 + 操作」聚合成类型:同一份购物车逻辑服务网页与脚本两处调用时不想复制两遍(封装成类);不同形状都要算面积但公式不同(继承 + 多态);自定义对象要能进排序、能比较、能打印(魔术方法)。
- 什么时候用到:任何业务实体建模(用户、订单、配置);给一组类建立共同接口(配合 抽象基类与协议);让自定义类型融入语言习惯(
len()、for、==的背后全是魔术方法)。类设计的取舍(封装约定、组合优于继承)见 面向对象进阶;数据承载类优先用 dataclass(见 数据类与 Pydantic)。
前置知识
- 类型注解与 mypy:建议先完成前一篇的学习
学习目标
- 掌握「1. 类与实例 (Class & Instance)」的核心机制、典型用法与常见陷阱
- 掌握「2. 属性与方法 (Properties & Methods)」的核心机制、典型用法与常见陷阱
- 掌握「3. 继承与多态 (Inheritance & Polymorphism)」的核心机制、典型用法与常见陷阱
- 掌握「4. 特殊方法 (Magic Methods)」的核心机制、典型用法与常见陷阱
- 掌握「5. 高级面向对象特性」的核心机制、典型用法与常见陷阱
1. 类与实例 (Class & Instance)
Python 是一门纯粹的面向对象语言,几乎所有的东西都是对象。
1.1 类的定义
class Person:
# 类属性 | Class attribute
species = "Human"
# 构造方法 | Constructor
def __init__(self, name, age):
self.name = name # 实例属性 | Instance attribute
self.age = age
# 实例方法 | Instance method
def greet(self):
return f"Hello, I am {self.name}"
# 实例化
p = Person("Alice", 30)
print(p.name) # 输出: Alice
print(p.age) # 输出: 30
print(p.greet()) # 输出: Hello, I am Alice
# 访问类属性
print(Person.species) # 输出: Human
print(p.species) # 输出: Human(实例也可以访问类属性)
# 修改类属性
Person.species = "Homo sapiens"
print(Person.species) # 输出: Homo sapiens
print(p.species) # 输出: Homo sapiens(实例的类属性也会改变)
# 修改实例的类属性(只会影响该实例)
p.species = "Human"
print(Person.species) # 输出: Homo sapiens
print(p.species) # 输出: Human1.2 类的文档字符串
class Person:
"""人类类"""
def __init__(self, name, age):
"""初始化方法"""
self.name = name
self.age = age
def greet(self):
"""打招呼方法"""
return f"Hello, I am {self.name}"
# 访问文档字符串
print(Person.__doc__) # 输出: 人类类
print(Person.__init__.__doc__) # 输出: 初始化方法
print(Person.greet.__doc__) # 输出: 打招呼方法2. 属性与方法 (Properties & Methods)
2.1 实例方法
实例方法是最常见的方法类型,它接收 self 作为第一个参数,指向当前实例。
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
"""计算圆的面积"""
return 3.14159 * self.radius ** 2
def circumference(self):
"""计算圆的周长"""
return 2 * 3.14159 * self.radius
# 使用实例方法
c = Circle(5)
print(c.area()) # 输出: 78.53975
print(c.circumference()) # 输出: 31.41592.2 类方法
类方法使用 @classmethod 装饰器,接收 cls 作为第一个参数,指向类本身。
class Person:
species = "Human"
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def get_species(cls):
"""获取物种信息"""
return cls.species
@classmethod
def create_adult(cls, name):
"""创建一个成年人实例"""
return cls(name, 18)
# 使用类方法
print(Person.get_species()) # 输出: Human
# 使用类方法创建实例
adult = Person.create_adult("Bob")
print(adult.name) # 输出: Bob
print(adult.age) # 输出: 182.3 静态方法
静态方法使用 @staticmethod 装饰器,不接收 self 或 cls 参数,是类的纯工具函数。
class Math:
@staticmethod
def add(a, b):
"""加法运算"""
return a + b
@staticmethod
def multiply(a, b):
"""乘法运算"""
return a * b
# 使用静态方法
print(Math.add(2, 3)) # 输出: 5
print(Math.multiply(2, 3)) # 输出: 6
# 也可以通过实例调用静态方法
m = Math()
print(m.add(4, 5)) # 输出: 92.4 私有属性和方法
在 Python 中,使用双下划线 __ 开头的属性和方法被视为私有的,外部无法直接访问。
class Person:
def __init__(self, name, age):
self.name = name # 公开属性
self.__age = age # 私有属性
def __private_method(self):
"""私有方法"""
return f"This is a private method"
def get_age(self):
"""获取年龄(通过公开方法访问私有属性)"""
return self.__age
def set_age(self, age):
"""设置年龄(通过公开方法修改私有属性)"""
if age > 0:
self.__age = age
# 使用
p = Person("Alice", 30)
print(p.name) # 输出: Alice
# print(p.__age) # 错误: 'Person' object has no attribute '__age'
# print(p.__private_method()) # 错误: 'Person' object has no attribute '__private_method'
# 通过公开方法访问私有属性
print(p.get_age()) # 输出: 30
p.set_age(31)
print(p.get_age()) # 输出: 31
# 注意: Python 实际上是通过名称修饰来实现私有性的
# 可以通过 _ClassName__private 来访问私有属性(不推荐)
print(p._Person__age) # 输出: 31
print(p._Person__private_method()) # 输出: This is a private method2.5 装饰器属性 (@property)
@property 装饰器可以将方法伪装成属性,使得可以像访问属性一样访问方法。
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
"""获取全名"""
return f"{self.first_name} {self.last_name}"
@full_name.setter
def full_name(self, name):
"""设置全名"""
first_name, last_name = name.split()
self.first_name = first_name
self.last_name = last_name
@full_name.deleter
def full_name(self):
"""删除全名"""
self.first_name = None
self.last_name = None
# 使用属性装饰器
p = Person("Alice", "Smith")
print(p.full_name) # 输出: Alice Smith
# 设置属性
p.full_name = "Bob Johnson"
print(p.first_name) # 输出: Bob
print(p.last_name) # 输出: Johnson
print(p.full_name) # 输出: Bob Johnson
# 删除属性
del p.full_name
print(p.first_name) # 输出: None
print(p.last_name) # 输出: None
# print(p.full_name) # 输出: None None3. 继承与多态 (Inheritance & Polymorphism)
3.1 单继承
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some generic sound"
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# 使用
animal = Animal("Generic Animal")
dog = Dog("Rex")
cat = Cat("Whiskers")
print(animal.speak()) # 输出: Some generic sound
print(dog.speak()) # 输出: Woof!
print(cat.speak()) # 输出: Meow!
# 检查继承关系
print(isinstance(dog, Animal)) # 输出:
print(issubclass(Dog, Animal)) # 输出:3.2 多继承
Python 支持多继承,一个子类可以继承多个父类。
class A:
def method(self):
return "A.method()"
class B:
def method(self):
return "B.method()"
def other_method(self):
return "B.other_method()"
class C(A, B):
pass
# 使用
c = C()
print(c.method()) # 输出: A.method()(按照 MRO 顺序,先 A 后 B)
print(c.other_method()) # 输出: B.other_method()
# 查看方法解析顺序 (MRO)
print(C.__mro__) # 输出: (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)3.3 super() 函数
super() 函数用于调用父类的方法,在继承中非常有用。
class Parent:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello from {self.name}"
class Child(Parent):
def __init__(self, name, age):
super().__init__(name) # 调用父类的 __init__ 方法
self.age = age
def greet(self):
parent_greeting = super().greet() # 调用父类的 greet 方法
return f"{parent_greeting}, I'm {self.age} years old"
# 使用
child = Child("Bob", 10)
print(child.greet()) # 输出: Hello from Bob, I'm 10 years old3.4 多态
多态是指不同类型的对象可以使用相同的接口。
def make_speak(animal):
"""让动物发声"""
print(animal.speak())
# 使用
make_speak(Dog("Rex")) # 输出: Woof!
make_speak(Cat("Whiskers")) # 输出: Meow!
make_speak(Animal("Generic")) # 输出: Some generic sound3.5 鸭子类型
Python 采用鸭子类型,不关注对象的具体类型,只关注对象是否具有所需的方法或属性。
class Duck:
def quack(self):
return "Quack!"
class Person:
def quack(self):
return "I'm quacking like a duck!"
def make_quack(obj):
"""让对象发出鸭叫声"""
if hasattr(obj, 'quack'):
print(obj.quack())
else:
print("This object can't quack")
# 使用
make_quack(Duck()) # 输出: Quack!
make_quack(Person()) # 输出: I'm quacking like a duck!4. 特殊方法 (Magic Methods)
特殊方法(也称为魔术方法或双下划线方法)是 Python 中具有特殊功能的方法,它们以双下划线开头和结尾。
4.1 基本特殊方法
class Person:
def __init__(self, name, age):
"""初始化方法"""
self.name = name
self.age = age
def __str__(self):
"""字符串表示(对用户友好)"""
return f"Person(name='{self.name}', age={self.age})"
def __repr__(self):
"""字符串表示(对开发者友好)"""
return f"Person('{self.name}', {self.age})"
def __eq__(self, other):
"""相等性比较"""
if not isinstance(other, Person):
return NotImplemented
return self.name == other.name and self.age == other.age
def __lt__(self, other):
"""小于比较"""
if not isinstance(other, Person):
return NotImplemented
return self.age < other.age
# 使用
p1 = Person("Alice", 30)
p2 = Person("Bob", 25)
p3 = Person("Alice", 30)
print(str(p1)) # 输出: Person(name='Alice', age=30)
print(repr(p1)) # 输出: Person('Alice', 30)
print(p1 == p2) # 输出: False
print(p1 == p3) # 输出:
print(p1 < p2) # 输出: False
print(p2 < p1) # 输出:4.2 容器相关的特殊方法
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
"""获取长度"""
return len(self.items)
def __getitem__(self, index):
"""获取元素"""
return self.items[index]
def __setitem__(self, index, value):
"""设置元素"""
self.items[index] = value
def __contains__(self, item):
"""检查元素是否存在"""
return item in self.items
# 使用
my_list = MyList([1, 2, 3, 4, 5])
print(len(my_list)) # 输出: 5
print(my_list[0]) # 输出: 1
my_list[0] = 10
print(my_list[0]) # 输出: 10
print(3 in my_list) # 输出:
print(100 in my_list) # 输出: False
# 支持迭代
for item in my_list:
print(item, end=" ") # 输出: 10 2 3 4 54.3 算术运算符特殊方法
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""加法运算"""
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""减法运算"""
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""乘法运算"""
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
# 使用
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # 输出: Vector(4, 6)
print(v1 - v2) # 输出: Vector(-2, -2)
print(v1 * 2) # 输出: Vector(2, 4)5. 高级面向对象特性
5.1 抽象基类
抽象基类(Abstract Base Class, ABC)用于定义接口,强制子类实现特定的方法。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
"""动物发声"""
pass
@abstractmethod
def move(self):
"""动物移动"""
pass
class Dog(Animal):
def speak(self):
return "Woof!"
def move(self):
return "Running"
class Cat(Animal):
def speak(self):
return "Meow!"
def move(self):
return "Walking"
# 使用
dog = Dog()
print(dog.speak()) # 输出: Woof!
print(dog.move()) # 输出: Running
cat = Cat()
print(cat.speak()) # 输出: Meow!
print(cat.move()) # 输出: Walking
# 不能实例化抽象基类
# animal = Animal() # 错误: Can't instantiate abstract class Animal with abstract methods move, speak5.2 混入类 (Mixins)
混入类是一种特殊的类,用于为其他类提供功能,而不是作为独立的类使用。
class SwimMixin:
def swim(self):
return "Swimming"
class FlyMixin:
def fly(self):
return "Flying"
class Duck(SwimMixin, FlyMixin):
def quack(self):
return "Quack!"
class Penguin(SwimMixin):
def walk(self):
return "Walking"
# 使用
duck = Duck()
print(duck.quack()) # 输出: Quack!
print(duck.swim()) # 输出: Swimming
print(duck.fly()) # 输出: Flying
penguin = Penguin()
print(penguin.swim()) # 输出: Swimming
print(penguin.walk()) # 输出: Walking
# print(penguin.fly()) # 错误: 'Penguin' object has no attribute 'fly'5.3 单例模式
单例模式确保一个类只有一个实例。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value=None):
if value is not None:
self.value = value
# 使用
s1 = Singleton("First")
print(s1.value) # 输出: First
# 第二次创建会返回同一个实例
s2 = Singleton("Second")
print(s2.value) # 输出: Second
print(s1 is s2) # 输出:
print(s1.value) # 输出: Second(因为 s1 和 s2 是同一个实例)5.4 类装饰器
类装饰器用于修改类的行为。
def debug(cls):
"""类装饰器:为类的所有方法添加调试信息"""
for name, method in list(cls.__dict__.items()):
if callable(method) and not name.startswith('__'):
def wrapper(*args, **kwargs):
print(f"Calling {name} with args: {args}, kwargs: {kwargs}")
result = method(*args, **kwargs)
print(f"{name} returned: {result}")
return result
setattr(cls, name, wrapper)
return cls
@debug
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
# 使用
calc = Calculator()
calc.add(2, 3) # 输出: Calling add with args: (<__main__.Calculator object at 0x...>, 2, 3), kwargs: {}
# add returned: 5
calc.multiply(4, 5) # 输出: Calling multiply with args: (<__main__.Calculator object at 0x...>, 4, 5), kwargs: {}
# multiply returned: 206. 面向对象编程的最佳实践
6.1 设计原则
- 单一职责原则: 一个类应该只有一个引起它变化的原因
- 开放-封闭原则: 对扩展开放,对修改封闭
- 里氏替换原则: 子类应该能够替换父类
- 依赖倒置原则: 依赖于抽象,而不是具体实现
- 接口隔离原则: 客户端不应该依赖它不需要的接口
6.2 代码风格
- 命名约定: 类名使用驼峰命名法(如
MyClass),方法和属性使用蛇形命名法(如my_method) - 文档: 为类、方法和属性添加文档字符串
- 缩进: 使用 4 个空格进行缩进
- 行长: 每行代码不超过 79 个字符
- 导入: 按标准库、第三方库、本地模块的顺序导入
6.3 常见陷阱
- 可变默认参数: 避免使用可变对象作为默认参数
- 循环引用: 注意避免循环引用导致的内存泄漏
- 过度继承: 避免深层继承层次结构
- 过度使用继承: 考虑使用组合而不是继承
- 魔法方法滥用: 只在必要时使用魔法方法
7. 实际应用示例
7.1 学生管理系统
class Student:
def __init__(self, id, name, age, grade):
self.id = id
self.name = name
self.age = age
self.grade = grade
def __str__(self):
return f"Student(id={self.id}, name='{self.name}', age={self.age}, grade={self.grade})"
class StudentManager:
def __init__(self):
self.students = []
def add_student(self, student):
"""添加学生"""
self.students.append(student)
def remove_student(self, student_id):
"""移除学生"""
self.students = [s for s in self.students if s.id != student_id]
def get_student(self, student_id):
"""获取学生"""
for student in self.students:
if student.id == student_id:
return student
return None
def get_all_students(self):
"""获取所有学生"""
return self.students
# 使用
manager = StudentManager()
# 添加学生
manager.add_student(Student(1, "Alice", 18, 90))
manager.add_student(Student(2, "Bob", 17, 85))
manager.add_student(Student(3, "Charlie", 19, 95))
# 获取所有学生
for student in manager.get_all_students():
print(student)
# 获取特定学生
student = manager.get_student(2)
print(f"Found student: {student}")
# 移除学生
manager.remove_student(2)
print("After removing student 2:")
for student in manager.get_all_students():
print(student)7.2 形状计算
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
"""计算面积"""
pass
@abstractmethod
def perimeter(self):
"""计算周长"""
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Triangle(Shape):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def area(self):
# 使用海伦公式计算面积
s = (self.a + self.b + self.c) / 2
return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
def perimeter(self):
return self.a + self.b + self.c
# 使用
shapes = [
Rectangle(5, 4),
Circle(3),
Triangle(3, 4, 5)
]
for shape in shapes:
print(f"{type(shape).__name__}: Area = {shape.area():.2f}, Perimeter = {shape.perimeter():.2f}")动手实践
练习一(预测题):不运行代码,判断输出:
class Counter:
count = 0 # 类属性
def __init__(self):
Counter.count += 1
self.serial = Counter.count
a = Counter()
b = Counter()
c = Counter()
print(Counter.count, a.serial, b.serial)
b.count = 99 # 这一行做了什么?
print(Counter.count, b.count)提示:b.count = 99 是读类属性还是写实例属性?
参考实现
输出:3 1 2 与 3 99。构造三次把类属性 Counter.count 累加到 3,三个实例的 serial 分别是 1、2、3。b.count = 99 是在 b 实例上新建同名实例属性(赋值永远写实例命名空间,不碰类属性)——类属性 Counter.count 仍是 3,b.count 读到的是实例的 99,a.count 读到的仍是类属性的 3。这演示了属性查找的顺序(实例 __dict__ → 类 → 父类)与「读会沿链爬、写只落实例」的不对称——类属性适合全类共享的常量与计数器,可变类属性(列表/字典)共享给所有实例是经典事故。
练习二(实战题):实现 Temperature 类:内部以开尔文存储,对外暴露 celsius property(读写,赋值低于 -273.15 时抛 ValueError);再实现 __repr__ 与 __eq__(温度相同即相等),让它能进 sorted()(提示:排序需要 __lt__,或用 functools.total_ordering 一行补齐全套比较)。
提示:property 的 getter/setter 见本篇属性与方法一节;total_ordering 只需实现 __eq__ 与一个方向比较。
参考实现
import functools
@functools.total_ordering
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius # 走 setter 校验
@property
def celsius(self) -> float:
return self._kelvin - 273.15
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("温度低于绝对零度")
self._kelvin = value + 273.15
def __repr__(self) -> str:
return f"Temperature({self.celsius:.1f}C)"
def __eq__(self, other) -> bool:
return isinstance(other, Temperature) and self._kelvin == other._kelvin
def __lt__(self, other) -> bool:
return self._kelvin < other._kelvin
temps = [Temperature(30), Temperature(-5), Temperature(20)]
print(sorted(temps))
# [Temperature(-5.0C), Temperature(20.0C), Temperature(30.0C)]
try:
Temperature(-300)
except ValueError as e:
print(e) # 温度低于绝对零度要点:__init__ 里 self.celsius = ... 走的是 setter——校验收口在一处,构造路径也逃不掉;total_ordering 用 __eq__ + __lt__ 推导出其余四种比较,省下四份样板;排序、相等、打印三种「融入语言习惯」分别由 __lt__/__eq__/__repr__ 提供,这正是魔术方法的设计动机。
练习三(实战题):写一个 PluginBase 体系:PluginBase 定义 name 属性与抽象方法 run;每个插件子类用类装饰器 @register 自动登记;主程序遍历注册表执行全部插件。对照 抽象基类与协议 的 __init_subclass__ 写法,说出两种注册时机的一个差异。
提示:类装饰器收类、改、返回;注册表是模块级列表。
参考实现
from abc import ABC, abstractmethod
PLUGINS: list["PluginBase"] = []
def register(cls):
PLUGINS.append(cls())
return cls
class PluginBase(ABC):
name: str = "unnamed"
@abstractmethod
def run(self) -> None: ...
@register
class HelloPlugin(PluginBase):
name = "hello"
def run(self) -> None:
print("hello from plugin")
@register
class ByePlugin(PluginBase):
name = "bye"
def run(self) -> None:
print("bye from plugin")
for p in PLUGINS:
p.run()差异:类装饰器是显式注册(忘了贴 @register 插件就不生效,好在意图可见);__init_subclass__ 是隐式注册(定义即注册,不会忘,但「谁被注册了」要靠读基类才知道)。框架收第三方插件偏好后者(少一步使用成本),团队内部代码偏好前者(意图显式、便于审查)。
练习四(找错题):这个 Rectangle 有两处问题,先找再修:
class Rectangle:
def __init__(self, width, height):
width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(3, 4)
print(r.area())提示:width = width 赋给了谁?
参考实现
class Rectangle:
def __init__(self, width: float, height: float):
self.width = width # 少了 self. 前缀:局部变量自赋值
self.height = height
def area(self) -> float:
return self.width * self.height两处问题:其一,width = width 只是把参数赋给局部变量自己,实例上根本没有 width 属性——area() 里 self.width 抛 AttributeError,这是「参数没挂到 self 上」的经典笔误,写 self.width = width;其二(顺带):height 虽然对了,但两行写法不一致本身就是该类笔误的温床,统一 self.x = x 形态并开 mypy 后,这类问题在静态检查就会被指出。
练习五(实战题):实现一个 Stack 类:魔术方法让它支持 len()、in(__contains__)、可迭代(从栈顶到栈底)、+ 合并两个栈(返回新栈);再用 dataclass 重写同一功能对比代码量(dataclass 用法见 数据类与 Pydantic)。
提示:__len__、__contains__、__iter__、__add__;内部用 list 存即可。
参考实现
class Stack:
def __init__(self, items=()):
self._items = list(items)
def push(self, item) -> None:
self._items.append(item)
def pop(self):
if not self._items:
raise IndexError("pop from empty stack")
return self._items.pop()
def __len__(self) -> int:
return len(self._items)
def __contains__(self, item) -> bool:
return item in self._items
def __iter__(self):
return reversed(self._items) # 栈顶(末尾)先出
def __add__(self, other: "Stack") -> "Stack":
if not isinstance(other, Stack):
return NotImplemented
return Stack(self._items + other._items)
s = Stack([1, 2, 3])
print(len(s), 2 in s) # 3 True
print(list(iter(s))) # [3, 2, 1]
print(list(s + Stack([4]))) # [4, 3, 2, 1]要点:__iter__ 返回 reversed(...)——列表的反向迭代器,栈顶优先的语义在迭代器里表达;__add__ 对不支持的类型返回 NotImplemented(不是 NotImplementedError)——把「试另一个操作数的反射方法」的机会还给解释器,这是运算符重载的标准协议;pop 空栈抛 IndexError 与 list 行为对齐。
自我检查
- 能解释实例属性与类属性的查找顺序,并预测「实例赋值同名属性」后的读取结果;
- 能写出 property 的 getter/setter 并把校验收口在 setter;
- 能用
functools.total_ordering或手写__eq__/__lt__让自定义对象可排序; - 能说出静态方法与类方法各自的使用现场;
- 能解释
__init__里漏写self.x = x的后果与排查方法; - 能给自定义类补上
__len__/__iter__/__contains__/__add__让它融入内建操作。