深入理解 Python 中的__slots__:优化内存与属性管控的利器

引言: 在 Python 面向对象编程中,__slots__是一个易被忽视但极具实用价值的特性 ------ 它既能大幅减少类实例的内存占用,又能严格管控实例可拥有的属性,避免意外的属性新增。本文聚焦__slots__这一核心知识点,从底层原理、使用方式到性能优化实战,带你掌握这一提升 Python 程序效率的关键技巧。

一、__slots__的核心背景:Python 实例的默认存储方式

Python 中普通类的实例默认使用字典(__dict__ 存储属性,字典的优势是灵活(可随时新增 / 删除属性),但缺点也很明显:

  • 内存占用高:字典为了哈希表的高效查找,会预留大量空闲空间,单个实例的内存开销远大于实际存储的属性数据;
  • 属性无管控 :可随意给实例新增属性,容易因拼写错误(如把name写成naem)引入难以排查的 bug。
python 复制代码
# 普通类实例的属性存储方式
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 25)
# 实例的属性存储在__dict__字典中
print(p.__dict__)  # 输出:{'name': 'Alice', 'age': 25}

# 可随意新增属性(灵活性高,但易出错)
p.gender = "female"
print(p.__dict__)  # 输出:{'name': 'Alice', 'age': 25, 'gender': 'female'}

__slots__的核心作用,就是用固定大小的数组替代字典存储属性,解决上述两个问题。

二、__slots__的基础使用

1. 基本定义与效果

__slots__是类级别的属性,需定义为字符串列表 / 元组,列出实例允许拥有的所有属性名。定义后:

  • 实例只能拥有__slots__中指定的属性,无法新增其他属性;
  • 实例不再生成__dict__(节省内存);
  • 实例的属性存储在数组中,访问速度更快。
python 复制代码
class Person:
    # 定义slots,指定实例仅允许拥有name和age属性
    __slots__ = ("name", "age")

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 正常使用指定的属性
p = Person("Bob", 30)
print(p.name)  # 输出:Bob
print(p.age)   # 输出:30

# 尝试新增未在slots中定义的属性,会报错
try:
    p.gender = "male"
except AttributeError as e:
    print(e)  # 输出:'Person' object has no attribute 'gender'

# 实例不再有__dict__属性
try:
    print(p.__dict__)
except AttributeError as e:
    print(e)  # 输出:'Person' object has no attribute '__dict__'

2. 多继承场景下的__slots__

若子类继承了多个父类,子类的__slots__会合并所有父类的__slots__,但需注意:

  • 子类未定义__slots__时,实例会恢复__dict__,父类的__slots__失效;
  • 子类定义__slots__时,实例的属性范围为 "子类 slots + 所有父类 slots"。
python 复制代码
# 父类1
class Parent1:
    __slots__ = ("a", "b")

# 父类2
class Parent2:
    __slots__ = ("b", "c")

# 子类:定义自己的slots
class Child(Parent1, Parent2):
    __slots__ = ("d",)  # 合并后允许的属性:a、b、c、d

c = Child()
c.a = 1
c.b = 2
c.c = 3
c.d = 4
print(c.a, c.b, c.c, c.d)  # 输出:1 2 3 4

# 子类:未定义slots(父类slots失效)
class Child2(Parent1):
    pass

c2 = Child2()
c2.a = 1
c2.xxx = 999  # 可新增任意属性
print(c2.xxx)  # 输出:999
print(c2.__dict__)  # 输出:{'xxx': 999}

三、__slots__的核心价值:内存与性能优化

1. 内存优化实战

对于需要创建大量实例的场景(如数据处理、爬虫存储),__slots__的内存优化效果极其显著。

python 复制代码
import sys

# 普通类(无slots)
class NormalClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# 带slots的类
class SlotsClass:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y

# 测试单个实例的内存占用
n = NormalClass(1, 2)
s = SlotsClass(1, 2)

print(f"普通实例内存占用:{sys.getsizeof(n) + sys.getsizeof(n.__dict__)} 字节")
# 输出约:56(实例本身) + 48(__dict__)= 104 字节

print(f"Slots实例内存占用:{sys.getsizeof(s)} 字节")
# 输出约:48 字节(无__dict__,仅存储x、y)

# 测试10万个实例的内存占用
import memory_profiler

@memory_profiler.profile
def create_normal_instances():
    # 创建10万个普通实例
    instances = [NormalClass(i, i+1) for i in range(100000)]
    return instances

@memory_profiler.profile
def create_slots_instances():
    # 创建10万个slots实例
    instances = [SlotsClass(i, i+1) for i in range(100000)]
    return instances

# 执行测试(运行后可看到内存使用差异)
# create_normal_instances()  # 约占用80MB+内存
# create_slots_instances()   # 约占用40MB+内存(节省约50%)

结论 :带__slots__的类实例,内存占用通常能减少 50% 以上,实例数量越多,优化效果越明显。

2. 访问性能优化

由于__slots__用数组存储属性(直接通过索引访问),而非字典(哈希查找),属性访问速度也会小幅提升。

python 复制代码
import timeit

# 测试属性访问速度
setup = """
class NormalClass:
    def __init__(self, x):
        self.x = x

class SlotsClass:
    __slots__ = ("x",)
    def __init__(self, x):
        self.x = x

n = NormalClass(10)
s = SlotsClass(10)
"""

# 测试普通实例访问属性
normal_time = timeit.timeit("n.x", setup=setup, number=10000000)
# 测试slots实例访问属性
slots_time = timeit.timeit("s.x", setup=setup, number=10000000)

print(f"普通实例访问耗时:{normal_time:.2f} 秒")
print(f"Slots实例访问耗时:{slots_time:.2f} 秒")
# 输出示例:普通实例耗时1.20秒,Slots实例耗时0.80秒(快约30%)

四、__slots__的使用注意事项

1. 保留特殊属性

若需要实例保留__dict____weakref__(弱引用),需在__slots__中显式声明:

python 复制代码
class Person:
    # 保留__dict__(允许新增属性) + 管控指定属性
    __slots__ = ("name", "age", "__dict__")

p = Person("Alice", 25)
p.gender = "female"  # 可新增属性(因__dict__存在)
print(p.gender)  # 输出:female
print(p.__dict__)  # 输出:{'gender': 'female'}

2. 不可变属性的实现

若需要属性只读,可结合propertyslots

python 复制代码
class ReadOnlyClass:
    __slots__ = ("_x",)  # 私有属性存储值

    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x

roc = ReadOnlyClass(10)
print(roc.x)  # 输出:10

# 尝试修改x会报错(无setter)
try:
    roc.x = 20
except AttributeError as e:
    print(e)  # 输出:can't set attribute

# 尝试直接修改私有属性也会报错(slots仅管控_x,但若命名为私有,外部不应访问)
try:
    roc._x = 20
except AttributeError as e:
    print(e)  # 输出:'ReadOnlyClass' object attribute '_x' is read-only

3. 适用场景与局限性

适用场景:

  • 需要创建大量实例的类(如数据模型、缓存对象);
  • 内存占用敏感的场景(如嵌入式 Python、低内存服务器);
  • 需要严格管控属性,避免意外新增属性的场景。

局限性:

  • 灵活性降低:无法动态新增属性(除非显式声明__dict__);
  • 仅影响实例:__slots__不限制类属性的新增;
  • 继承需注意:子类未定义__slots__会恢复__dict__,导致父类优化失效。

总结

  1. __slots__是类级别的属性,用于指定实例允许拥有的属性列表,核心作用是替代__dict__存储属性
  2. 主要优势:大幅减少实例内存占用(通常 50% 以上)、小幅提升属性访问速度、严格管控实例属性;
  3. 使用要点:多继承时会合并父类 slots,需保留__dict__可允许动态新增属性,结合 property 可实现只读属性;
  4. 适用场景:大量实例创建、内存敏感场景、属性管控需求,普通小体量类无需过度使用
相关推荐
机器懒得学习1 天前
通达信分时资金流向分析系统【附源码】
开发语言·python
mg6681 天前
0基础开发学习python工具_____用 Python 从零写一个贪吃蛇游戏:完整实现 + 打包成 .exe(附源码)
python·游戏·pygame·python开发
@zulnger1 天前
python 学习笔记(对象的方法)
笔记·python·学习
B站计算机毕业设计之家1 天前
大数据毕业设计:基于python图书数据分析可视化系统 书籍大屏 爬虫 清洗 可视化 当当网书籍数据分析 Django框架 图书推荐 大数据
大数据·爬虫·python·机器学习·自然语言处理·数据分析·课程设计
Elaine3361 天前
【 基于 TensorFlow+CNN 的水果图像识别系统设计与实现】
人工智能·python·深度学习·计算机视觉·cnn·tensorflow
2401_841495641 天前
【机器学习】深度神经网络(DNN)
人工智能·python·深度学习·神经网络·机器学习·dnn·深度神经网络
njsgcs1 天前
ai流水线式调用命令
人工智能·python
老歌老听老掉牙1 天前
Python JSON 配置文件读写简明指南
python·json
Salt_07281 天前
DAY 54 对抗生成网络
网络·python·神经网络·机器学习·计算机视觉