You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python 3.8-3.14中基于类型注解生成__slots__

问题描述

我想创建基于__slots__的类,不需要重复已经在类型注解里列过的属性名。

在Python 3.14之前,我可以这么实现:

class C:
    foo: int
    bar: int
    __slots__ = (*__annotations__,)
    not_a_slot: ClassVar[str] = 'some class variable'

但Python 3.14改用了延迟求值注解,这意味着赋值__slots__的时候,__annotations__字典还不存在。另外,Python官方最佳实践也建议3.10之后不要直接用__annotations__。

@dataclass(slots=True)适用于Python 3.10及以上版本,但有很多副作用。可惜我的代码要适配大量遗留系统,必须同时兼容Python 3.8和3.14。

有没有兼容3.8且面向未来的方法,能基于部分注解属性生成__slots__,还不用重复属性名?

我自己找到的唯一办法是写类装饰器生成带__slots__的类,代码如下:

from typing import ClassVar, get_origin

try:
    from inspect import get_annotations
except ImportError:

    def get_annotations(x):
        return x.__annotations__


def annotations_to_slots(klass):
    class Out(klass):
        __slots__ = tuple(
            name
            for name, declared_type in get_annotations(klass).items()
            if get_origin(declared_type) is not ClassVar
        )

    Out.__name__ = klass.__name__
    try:
        Out.__doc__ = klass.__doc__
    except AttributeError:
        pass
    return Out


@annotations_to_slots
class C:
    __slots__ = ()  # will be filled in by @annotations_to_slots
    foo: int
    bar: int
    not_a_slot: ClassVar[str] = 'not a slot'

这个方法虽然能用,但有几个弊端:__slots__最终在自动生成的类上,而不是定义字段的类;定义类必须写__slots__ = (),就算加了注释还是容易误导;MRO里多了一个类,会让继承关系变模糊。

有没有更优的实现方案?


更优解决方案:使用元类

相比装饰器,元类可以直接在类定义阶段修改原类,避免生成额外的子类,解决你提到的所有弊端。以下是兼容Python 3.8到3.14的实现:

from typing import ClassVar, get_origin
import sys

try:
    from inspect import get_annotations
except ImportError:
    def get_annotations(cls):
        return cls.__annotations__ if hasattr(cls, '__annotations__') else {}

class SlotsFromAnnotationsMeta(type):
    def __new__(cls, name, bases, namespace):
        # 先创建类的基础实例,确保注解被处理(兼容3.14延迟求值)
        new_cls = super().__new__(cls, name, bases, namespace)
        # 获取所有注解,过滤掉ClassVar类型的属性
        annotations = get_annotations(new_cls)
        slots = tuple(
            attr_name for attr_name, attr_type in annotations.items()
            if get_origin(attr_type) is not ClassVar
        )
        # 直接给原类设置__slots__,如果已有__slots__则合并(可选)
        if hasattr(new_cls, '__slots__') and new_cls.__slots__:
            new_cls.__slots__ = tuple(set(new_cls.__slots__) | set(slots))
        else:
            new_cls.__slots__ = slots
        return new_cls

# 使用示例
class C(metaclass=SlotsFromAnnotationsMeta):
    foo: int
    bar: int
    not_a_slot: ClassVar[str] = 'not a slot'

# 测试验证
c = C()
c.foo = 1
c.bar = 2
# 下面这行会报错,符合预期,因为not_a_slot是ClassVar不是实例属性
# c.not_a_slot = 'test'

方案优势

  • 直接修改原类,__slots__定义在你编写的类本身,不会生成额外子类,MRO清晰
  • 不需要在类里写__slots__ = (),完全由元类自动生成
  • 兼容Python 3.8到3.14:
    • 3.8及更早版本用__annotations__降级处理
    • 3.10+使用标准库的inspect.get_annotations处理延迟求值注解
  • 支持合并已有的__slots__(如果需要手动添加非注解属性的slot)

额外优化:支持排除指定属性

如果需要排除某些注解属性(比如除了ClassVar之外的其他不需要作为slot的属性),可以给类加一个类属性__exclude_from_slots__来指定,元类里读取该属性过滤:

class SlotsFromAnnotationsMeta(type):
    def __new__(cls, name, bases, namespace):
        new_cls = super().__new__(cls, name, bases, namespace)
        annotations = get_annotations(new_cls)
        exclude = getattr(new_cls, '__exclude_from_slots__', set())
        slots = tuple(
            attr_name for attr_name, attr_type in annotations.items()
            if get_origin(attr_type) is not ClassVar and attr_name not in exclude
        )
        # 合并已有slots逻辑不变
        if hasattr(new_cls, '__slots__') and new_cls.__slots__:
            new_cls.__slots__ = tuple(set(new_cls.__slots__) | set(slots))
        else:
            new_cls.__slots__ = slots
        # 移除临时的exclude属性(可选)
        if '__exclude_from_slots__' in namespace:
            delattr(new_cls, '__exclude_from_slots__')
        return new_cls

# 使用示例:排除baz属性
class D(metaclass=SlotsFromAnnotationsMeta):
    foo: int
    bar: int
    baz: str
    __exclude_from_slots__ = {'baz'}
    not_a_slot: ClassVar[str] = 'class var'

内容的提问来源于stack exchange,提问作者Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 18:43:08