Skip to content

Python 面向对象 ​

1. new vs init ​

python
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=None):
        self.value = value

a = Singleton('first')
b = Singleton('second')
print(a is b)       # True, 同一实例
print(a.value)      # second, __init__ 被调用了两次

执行顺序:

ClassName(args)  →  __new__(cls, args)  →  __init__(self, args)
                     返回新实例              初始化该实例
特性__new____init__
调用时机实例创建前实例创建后
参数cls (类本身)self (新实例)
返回值必须返回实例必须返回 None
用途控制实例创建(单例、不可变子类)初始化实例属性
python
# __new__ 用于不可变类型的子类化
class PositiveInt(int):
    def __new__(cls, value):
        return super().__new__(cls, abs(value))

x = PositiveInt(-5)
print(x, type(x))  # 5 <class '__main__.PositiveInt'>

2. MRO 与 C3 线性化 ​

Python 使用 C3 线性化算法 确定方法解析顺序(MRO):

python
class A:
    def greet(self):
        return 'A'

class B(A):
    def greet(self):
        return 'B'

class C(A):
    def greet(self):
        return 'C'

class D(B, C):
    pass

print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

d = D()
print(d.greet())  # B, 按 MRO 顺序查找

C3 线性化规则:

  1. 子类优先于父类
  2. 多个父类按声明顺序
  3. 保持局部优先级(不违反任何基类的线性化顺序)
python
# C3 的约束验证
class X:
    def method(self):
        return 'X'

class Y(X):
    def method(self):
        return 'Y'

class Z(X):
    def method(self):
        return 'Z'

class W(Y, Z):
    pass

# W.mro() = [W, Y, Z, X, object]
# Y 在 Z 前(声明顺序),Z 在 X 前(继承顺序)

print(W().method())  # Y
python
# 不合法的 MRO —— 会抛出 TypeError
class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B, C):
    pass
class E(C, B):  # 与 D 中 B 在 C 前矛盾
    pass

# class F(D, E):  # TypeError: Cannot create a consistent method resolution order
#     pass

3. 元类 — type ​

python
# type 是所有类的类
print(type(42))           # <class 'int'>
print(type(int))          # <class 'type'>
print(type(type))         # <class 'type'>

# type() 的动态创建能力
# type(name, bases, dict) -> new class

def init_method(self, name):
    self.name = name

def repr_method(self):
    return f'Person({self.name})'

Person = type('Person', (), {
    '__init__': init_method,
    '__repr__': repr_method,
})

p = Person('Alice')
print(p)  # Person(Alice)

自定义元类 ​

python
class RegistryMeta(type):
    _registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # 跳过基类本身
            mcs._registry[name] = cls
        return cls

    @classmethod
    def get_registry(mcs):
        return mcs._registry.copy()

class Plugin(metaclass=RegistryMeta):
    pass

class AuthPlugin(Plugin):
    pass

class CachePlugin(Plugin):
    pass

print(RegistryMeta.get_registry())
# {'AuthPlugin': <class 'AuthPlugin'>, 'CachePlugin': <class 'CachePlugin'>}

4. init_subclass ​

Python 3.6+ 的 __init_subclass__ 可以替代元类的部分功能:

python
class PluginBase:
    _plugins = {}

    def __init_subclass__(cls, plugin_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        name = plugin_name or cls.__name__
        PluginBase._plugins[name] = cls

class Auth(PluginBase, plugin_name='auth'):
    pass

class Cache(PluginBase, plugin_name='cache'):
    pass

print(PluginBase._plugins)
# {'auth': <class 'Auth'>, 'cache': <class 'Cache'>}

__init_subclass__ vs 元类:

特性__init_subclass__元类
复杂度低高
能力子类注册、参数验证完全控制类的创建过程
推荐度优先使用仅在需要深度控制时使用

5. 描述符协议 — 数据描述符 vs 面数据描述符 ​

python
# 描述符协议:实现了以下任一方法的对象
# __get__(self, obj, objtype=None)
# __set__(self, obj, value)
# __delete__(self, obj)

# 数据描述符:同时实现 __get__ 和 __set__(优先级更高)
class Validated:
    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)

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)):
            raise TypeError(f'{self.name} must be numeric')
        obj.__dict__[self.name] = value

class Temperature:
    celsius = Validated()
    fahrenheit = Validated()

    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5 / 9

t = Temperature(100)
print(t.fahrenheit)  # 212.0

# t.celsius = 'hot'  # TypeError: celsius must be numeric

属性查找优先级:

数据描述符 > 实例 __dict__ > 非数据描述符 > 类属性
python
# 非数据描述符:只实现 __get__
class Method:
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self.func
        return lambda *args, **kwargs: self.func(obj, *args, **kwargs)

class MyClass:
    @Method
    def greet(self):
        return 'hello'

print(MyClass().greet())  # hello

6. property 实现原理 ​

property 是一个数据描述符:

python
# property 实际上是一个类
class property:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc or (fget.__doc__ if fget else None)

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    # 装饰器语法
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)
python
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError('radius must be non-negative')
        self._radius = value

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
print(c.area)    # 78.53981633974483
c.radius = 10
print(c.area)    # 314.1592653589793

7. 类方法 / 实例方法 / 静态方法 ​

python
class MyClass:
    class_var = 'class'

    def instance_method(self):
        """第一个参数是实例 (self)"""
        return f'instance: {self}, class_var: {self.class_var}'

    @classmethod
    def class_method(cls):
        """第一个参数是类 (cls)"""
        return f'class: {cls}, class_var: {cls.class_var}'

    @staticmethod
    def static_method():
        """没有隐式参数"""
        return 'static method'

obj = MyClass()
print(obj.instance_method())  # instance: <__main__.MyClass object>, ...
print(obj.class_method())     # class: <class '__main__.MyClass'>, ...
print(obj.static_method())    # static method

# 继承中的差异
class Child(MyClass):
    class_var = 'child_class'

c = Child()
print(c.instance_method())  # 使用子类实例,但 class_var 查找会沿 MRO
print(c.class_method())     # cls 是 Child,不是 MyClass
print(c.static_method())    # 与父类相同

8. ABC(抽象基类) ​

python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

    def description(self):
        return f'{self.__class__.__name__} with area {self.area():.2f}'

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius ** 2

    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

# s = Shape()  # TypeError: Can't instantiate abstract class
c = Circle(5)
print(c.description())  # Circle with area 78.54

# 注册(不强制实现)
from abc import ABCMeta
class MyInterface(ABCMeta):
    pass

class MyABC(metaclass=MyInterface):
    pass

9. Mixin 模式 ​

python
import json

class JsonMixin:
    """Mixin: 提供 JSON 序列化/反序列化能力"""
    def to_json(self):
        return json.dumps(self.__dict__, ensure_ascii=False, indent=2)

    @classmethod
    def from_json(cls, data):
        return cls(**json.loads(data))

class LogMixin:
    """Mixin: 提供日志能力"""
    def log(self, message):
        print(f'[{self.__class__.__name__}] {message}')

class User(JsonMixin, LogMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age

u = User('Alice', 30)
print(u.to_json())
# {
#   "name": "Alice",
#   "age": 30
# }
u.log('User created')
# [User] User created

# 从 JSON 恢复
u2 = User.from_json('{"name": "Bob", "age": 25}')
print(u2.name)  # Bob

Mixin 设计原则:

  • 不应有自己的 __init__
  • 不应与业务类产生强耦合
  • 通常不直接实例化
  • 放在 MRO 左侧(继承列表靠前位置)

10. dataclass ​

python
from dataclasses import dataclass, field, asdict, astuple
from typing import List

@dataclass
class Point:
    x: float
    y: float

    def distance_to(self, other: 'Point') -> float:
        return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5

p1 = Point(1.0, 2.0)
p2 = Point(4.0, 6.0)
print(p1)           # Point(x=1.0, y=2.0)
print(p1 == p2)     # False (自动生成 __eq__)
print(p1.distance_to(p2))  # 5.0

# 不可变 dataclass
@dataclass(frozen=True)
class FrozenPoint:
    x: float
    y: float

fp = FrozenPoint(1, 2)
# fp.x = 3  # FrozenInstanceError

# 更多选项
@dataclass(order=True, slots=True)  # 3.10+
class Student:
    name: str
    grade: int = field(compare=True)  # 参与比较
    age: int = field(compare=False)   # 不参与比较
    tags: List[str] = field(default_factory=list)

s1 = Student('Alice', grade=90, age=20)
s2 = Student('Bob', grade=85, age=22)
print(s1 > s2)  # True, 按 grade 比较

# 转换
print(asdict(s1))   # {'name': 'Alice', 'grade': 90, 'age': 20, 'tags': []}
print(astuple(s1))  # ('Alice', 90, 20, [])

dataclass 生成的方法:

  • __init__:初始化
  • __repr__:字符串表示
  • __eq__:值比较
  • __hash__:哈希(需 frozen=True 或 eq=True, frozen=True)
  • __lt__, __le__ 等:排序(需 order=True)

面试要点:dataclass 本质上是装饰器,它通过 __init_subclass__ 在类创建时注入方法,底层仍然使用普通属性存储,与 namedtuple 不同。