类与面向对象
问题
Python 的面向对象有哪些核心概念?MRO 如何解析?魔术方法有哪些?描述符协议是什么?
答案
类的基础
class Person:
# 类变量:所有实例共享
species = "Homo sapiens"
def __init__(self, name: str, age: int):
# 实例变量:每个实例独立
self.name = name
self.age = age
def greet(self) -> str:
return f"I'm {self.name}, {self.age} years old"
@classmethod
def from_birth_year(cls, name: str, birth_year: int) -> "Person":
"""类方法:可作为替代构造函数"""
return cls(name, 2026 - birth_year)
@staticmethod
def is_adult(age: int) -> bool:
"""静态方法:与类相关但不需要实例/类引用"""
return age >= 18
@property
def info(self) -> str:
"""属性:像访问属性一样调用方法"""
return f"{self.name}({self.age})"
三大特性
封装:通过命名约定控制访问级别
class Account:
def __init__(self, balance: float):
self._balance = balance # 约定私有(单下划线),外部可访问但不建议
self.__secret = "hidden" # 名称改写(双下划线)→ _Account__secret
@property
def balance(self) -> float:
return self._balance
继承:子类继承父类的属性和方法
class Animal:
def speak(self) -> str:
raise NotImplementedError
class Dog(Animal):
def speak(self) -> str:
return "汪汪"
class Cat(Animal):
def speak(self) -> str:
return "喵喵"
多态:不同类型对象对同一方法有不同实现
def animal_sound(animal: Animal) -> str:
return animal.speak() # 运行时根据实际类型调用
animal_sound(Dog()) # "汪汪"
animal_sound(Cat()) # "喵喵"
MRO(方法解析顺序)
Python 使用 C3 线性化算法解决多继承的方法查找顺序:
class A:
def method(self): return "A"
class B(A):
def method(self): return "B"
class C(A):
def method(self): return "C"
class D(B, C):
pass
print(D.mro())
# [D, B, C, A, object]
# D 先找 B,再找 C,再找 A,最后 object
d = D()
d.method() # "B" — 按 MRO 顺序,B 先于 C
super() 与 MRO:
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
super().method() # 按 MRO 调用下一个,不一定是父类
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
D().method()
# D → B → C → A (按 MRO 顺序链式调用)
魔术方法(Dunder Methods)
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
# 字符串表示
def __repr__(self) -> str:
"""开发者看到的表示,应能重建对象"""
return f"Vector({self.x}, {self.y})"
def __str__(self) -> str:
"""用户看到的表示"""
return f"({self.x}, {self.y})"
# 算术运算
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
# 比较
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self) -> int:
return hash((self.x, self.y))
# 容器协议
def __len__(self) -> int:
return 2
def __getitem__(self, index: int) -> float:
return (self.x, self.y)[index]
# 布尔值
def __bool__(self) -> bool:
return self.x != 0 or self.y != 0
# 可调用
def __call__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 == v2) # False
print(len(v1)) # 2
print(v1[0]) # 1
print(v1(3)) # (3, 6) — 实例当函数调用
描述符协议
描述符是实现了 __get__、__set__、__delete__ 中任意一个方法的对象:
class Validated:
"""数据描述符:验证赋值"""
def __init__(self, min_value: float = 0):
self.min_value = min_value
self.name = ""
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name, 0)
def __set__(self, obj, value):
if value < self.min_value:
raise ValueError(f"{self.name} 不能小于 {self.min_value}")
obj.__dict__[self.name] = value
class Product:
price = Validated(min_value=0) # 描述符实例作为类变量
quantity = Validated(min_value=0)
p = Product()
p.price = 9.99 # ✅
p.quantity = -1 # ❌ ValueError: quantity 不能小于 0
property 就是描述符
@property 实际上是一个数据描述符的语法糖,底层实现了 __get__、__set__、__delete__。
__slots__
class Point:
__slots__ = ("x", "y") # 限制实例属性,禁止 __dict__
def __init__(self, x: float, y: float):
self.x = x
self.y = y
p = Point(1, 2)
p.z = 3 # ❌ AttributeError
# 优势:节省约 40% 内存,属性访问略快
常见面试问题
Q1: __new__ 和 __init__ 的区别?
答案:
__new__:创建实例,是类方法,返回新实例对象__init__:初始化实例,是实例方法,设置属性- 调用顺序:
__new__→__init__
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
a = Singleton(1)
b = Singleton(2)
print(a is b) # True — 同一实例
print(a.value) # 2 — __init__ 被调用了两次
Q2: 类方法、静态方法和实例方法的区别?
答案:
| 方法类型 | 装饰器 | 第一个参数 | 能否访问实例 | 能否访问类 |
|---|---|---|---|---|
| 实例方法 | 无 | self(实例) | ✅ | ✅ |
| 类方法 | @classmethod | cls(类) | ❌ | ✅ |
| 静态方法 | @staticmethod | 无 | ❌ | ❌ |
类方法常用作替代构造函数;静态方法是与类相关的工具函数。
Q3: __str__ 和 __repr__ 的区别?
答案:
__repr__:面向开发者,应该无歧义,理想情况下可以eval()还原对象__str__:面向用户,可读性优先print()优先调用__str__;交互式解释器和容器内元素使用__repr__
import datetime
d = datetime.date(2026, 3, 28)
repr(d) # "datetime.date(2026, 3, 28)" — 可以 eval 还原
str(d) # "2026-03-28" — 人类可读
Q4: 什么是抽象基类(ABC)?
答案:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
"""子类必须实现"""
pass
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius ** 2
# Shape() # ❌ TypeError: 不能实例化抽象类
c = Circle(5) # ✅
Q5: Python 如何实现私有属性?
答案:
Python 没有真正的私有,使用命名约定:
| 前缀 | 效果 | 示例 |
|---|---|---|
| 无前缀 | 公开 | self.name |
_ 单下划线 | 约定私有 | self._internal |
__ 双下划线 | 名称改写 | self.__secret → self._Class__secret |
双下划线会被改写为 _类名__属性名,目的是避免子类意外覆盖,而非安全机制。