Skip to content

类与元编程

property、__slots__、dataclass、__new__ 与 __init__、元类与属性拦截。

Updated View as Markdown
For humans

类与元编程

校招考察频度:☆ 中频(面向对象与进阶拓展) 复习主线:Python 面试在面向对象进阶层面有五大经典考点:日常必用的 property 与 __slots__ 性能优化、开箱即用的 dataclass 数据建模、对象生命周期核心钩子 __new__ vs __init__、属性访问拦截器 __getattr__ vs __getattribute__,以及了解级的元类(能说出用途即可,不要求手写)。


1. property:属性即方法

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

    @property
    def area(self):            # 读: 像属性一样访问
        return 3.14 * self._radius ** 2

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

    @radius.setter
    def radius(self, value):   # 写: 校验入口
        if value <= 0:
            raise ValueError("半径必须为正")
        self._radius = value
  • 把方法包装成属性访问,调用方无感知(c.area 不是 c.area())。
  • setter 是校验和副作用的最佳位置:统一在赋值处拦截非法值。
  • 只定义 getter 不定义 setter 就是只读属性。
  • 本质是装饰器 + 描述符协议(Descriptor Protocol)。

2. slots:省内存

class Point:
    __slots__ = ("x", "y")     # 固定属性名
    def __init__(self, x, y):
        self.x, self.y = x, y
  • 默认 Python 类自带 __dict__(属性字典),每个实例都包含一个动态哈希表,内存开销大。
  • __slots__ 显式声明固定属性,底层用紧凑的 C 数组代替字典存放属性引用,大幅节省内存(在数百万量级对象时节省可达 40%~50%)。
  • 代价:实例不能随意动态添加未声明的属性(没有 __dict__)、多重继承存在限制。
  • 适用:百万级并发数据传输对象(DTO)。常规业务开发无需过度使用。

3. dataclass:现代数据类

from dataclasses import dataclass, field

@dataclass
class User:
    id: int
    name: str
    tags: list[str] = field(default_factory=list)   # 可变默认值的正确写法
  • 自动生成 __init__、__repr__、__eq__,彻底告别重复样板代码。
  • frozen=True 生成不可变数据类(只读且可哈希,可用作字典的 Key)。
  • field(default_factory=...) 杜绝共享同一个可变列表/字典的默认参数陷阱。
  • 业务领域实体优先使用 dataclass;若涉及 HTTP 请求参数严格校验与序列化,换用 Pydantic。

4. new vs init:对象创建全生命周期

在 Python 中,调用 User() 创建对象时,底层经历了 构造(创建实例) 和 初始化(赋予属性) 两个不同阶段:

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        # 1. 负责【创建并返回】实例对象(在内存中真正分配空间)
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, name=None):
        # 2. 负责【初始化】实例属性(拿到 __new__ 返回的 self 进行赋值)
        if name:
            self.name = name
维度__new__(cls, ...)__init__(self, ...)
角色构造器(Constructor),负责对象分配与诞生初始化器(Initializer),负责属性填充与设置
方法类型静态方法(首个参数是类对象 cls)实例方法(首个参数是实例 self)
返回值必须返回一个实例对象严禁有返回值(必须返回 None)
调用时机先于 __init__ 执行,返回值传递给 __init__ 的 self只有当 __new__ 返回当前类的实例时才会被触发
经典场景单例模式、继承不可变类型(int/str/tuple)定制构造日常 99% 业务对象的属性赋值

5. getattr vs getattribute:属性拦截机制

class DynamicProxy:
    def __init__(self):
        self.real_data = {"host": "127.0.0.1", "port": 8080}

    def __getattribute__(self, name):
        # 无条件拦截:任何属性访问(包括 real_data)都会先走到这里
        # 注意:访问内部属性必须用 super(),否则造成无限递归死循环!
        print(f"[Log] 访问属性: {name}")
        return super().__getattribute__(name)

    def __getattr__(self, name):
        # 兜底拦截:只有在常规查找(实例字典、类属性、父类)都找不到该属性时才触发
        if name in self.real_data:
            return self.real_data[name]
        raise AttributeError(f"未找到属性 '{name}'")

p = DynamicProxy()
print(p.host)   # 触发 __getattribute__ 打印日志 -> 实例无 host 属性 -> 触发 __getattr__ 返回 "127.0.0.1"
  • __getattribute__:无条件第一优先级。不管属性存不存在都会触发。容易引发无限递归(RecursionError),必须通过 super().__getattribute__(name) 读取。
  • __getattr__:降级兜底器。只有当常规查找完全失败、即将抛出 AttributeError 之前才被调用。最适合做 RPC 客户端动态方法路由、ORM 字段动态映射或配置默认值回退。

6. 元类:了解级

元类(metaclass)是“创建类的类”。class 语句在底层执行流程:

  1. 解释器调用元类(默认 type)。
  2. type.__new__ 创建类对象(类名、父类元组、类命名空间字典)。
  3. type.__init__ 初始化类对象。
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]

知道什么场景用:在类创建时统一注入行为,如 ORM 的模型自动注册与字段映射(SQLAlchemy / Django Model)、插件自动收集。业务开发中切忌滥用,装饰器与描述符几乎总能更清晰地替代元类。


面试追问

  1. __new__ 和 __init__ 的区别? __new__ 是静态方法负责创建并返回对象实例,__init__ 是实例方法负责属性初始化。单例模式通常在 __new__ 中拦截。
  2. __getattr__ 和 __getattribute__ 什么时候被调用? __getattribute__ 无条件每次都被调用;__getattr__ 仅在常规查找找不到属性抛出 AttributeError 之前作为兜底被调用。
  3. __slots__ 为什么能省内存? 禁用了实例自带的 __dict__ 哈希表,用连续定长数组存放属性指针。缺点是无法动态添加额外属性。
  4. property 解决什么问题? 方法当属性访问,可以在 getter/setter 中加入参数校验与计算逻辑,对外部调用方保持 API 兼容。
  5. 元类是什么?什么时候用? 元类是创建类的类(type 的子类)。常用于大型框架(ORM、RPC)在类被导入和定义时动态扫描字段、生成 SQL 模式。
Navigation

Type to search…

↑↓ navigate↵ selectEsc close