OOP 面向对象

__new__ vs __init__

__new____init__
作用创建对象(分配内存)初始化对象(设置属性)
参数cls(类本身)self(实例)
返回值必须返回实例无返回值(None)
调用顺序
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__ 被调用了两次)

魔术方法(Magic Methods)

常用魔术方法

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"
 
    def __str__(self):
        return f"({self.x}, {self.y})"
 
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
 
    def __len__(self):
        return 2
 
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
 
    def __hash__(self):
        return hash((self.x, self.y))
 
    def __getitem__(self, index):
        return (self.x, self.y)[index]
 
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)      # (4, 6)
print(len(v1))      # 2
print(v1[0])        # 1

__repr__ vs __str__

  • __repr__:开发者视角,repr(obj),交互式解释器显示,应能重建对象
  • __str__:用户视角,str(obj)print() 调用
  • 只定义 __repr__ 时,str() 也会用它;反之不行

继承与 MRO

MRO(方法解析顺序)

Python 3 使用 C3 线性化算法确定多继承时方法查找顺序。

class A:
    def method(self): print("A")
 
class B(A):
    def method(self): print("B")
 
class C(A):
    def method(self): print("C")
 
class D(B, C):
    pass
 
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
 
D().method()  # "B"(按 MRO 顺序,B 在 C 前)

super() 的正确用法

class Animal:
    def __init__(self, name):
        self.name = name
 
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类 __init__
        self.breed = breed
 
class GuideDog(Dog):
    def __init__(self, name, breed, owner):
        super().__init__(name, breed)
        self.owner = owner

@staticmethod vs @classmethod vs 实例方法

class MyClass:
    class_var = "I'm a class variable"
 
    def instance_method(self):
        # 访问实例和类
        print(f"Instance: {self}, Class: {self.__class__}")
 
    @classmethod
    def class_method(cls):
        # 只能访问类,不能访问实例
        # 常用于工厂方法、替代构造器
        print(f"Class: {cls}")
        return cls()  # 创建实例
 
    @staticmethod
    def static_method(x, y):
        # 既不能访问实例也不能访问类
        # 相当于放在类里的普通函数
        return x + y
 
# 工厂方法是 classmethod 最典型的用途
class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day
 
    @classmethod
    def from_string(cls, date_string):
        year, month, day = map(int, date_string.split('-'))
        return cls(year, month, day)
 
d = Date.from_string("2024-01-15")

Metaclass(元类)

类也是对象,元类是创建类的类

# 默认元类是 type
print(type(int))    # <class 'type'>
print(type(str))    # <class 'type'>
 
class MyClass:
    pass
print(type(MyClass))  # <class 'type'>
 
# 用 type 动态创建类
Dog = type("Dog", (object,), {"bark": lambda self: "Woof!"})
dog = Dog()
print(dog.bark())  # "Woof!"

自定义元类:

class SingletonMeta(type):
    _instances = {}
 
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
 
class Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "connected"
 
db1 = Database()
db2 = Database()
print(db1 is db2)  # True

数据类(dataclass)

Python 3.7+ 简化 OOP 样板代码:

from dataclasses import dataclass, field
 
@dataclass(frozen=True)   # frozen=True 使其不可变(可哈希)
class Point:
    x: float
    y: float
    z: float = 0.0
    tags: list = field(default_factory=list)  # 可变默认值必须用 field
 
    def distance(self):
        return (self.x**2 + self.y**2 + self.z**2) ** 0.5
 
p = Point(1.0, 2.0)
print(p)           # Point(x=1.0, y=2.0, z=0.0, tags=[])
print(p.distance()) # 2.23...

面试高频题

Q: 如何实现单例模式?(有多种方式,面试官可能追问)

# 方式1:__new__
# 方式2:模块级变量(Python 模块天然是单例)
# 方式3:元类(见上)
# 方式4:装饰器
def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance
 
@singleton
class Config:
    pass

Q: __slots__ 的作用?

class WithSlots:
    __slots__ = ['x', 'y']  # 限制实例属性,用 tuple 而非 dict 存储
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
# 优点:内存占用更小(无 __dict__),属性访问更快
# 缺点:不能动态添加属性,不能有默认值(除非用描述符)
w = WithSlots(1, 2)
w.z = 3  # AttributeError!