Python Mastery 项目:深入理解Python容器内存优化技巧

Python Mastery 项目:深入理解Python容器内存优化技巧

python-mastery Advanced Python Mastery (course by @dabeaz) python-mastery 项目地址: https://gitcode.com/gh_mirrors/py/python-mastery

前言

在Python编程中,理解数据结构的内部工作机制对于编写高效程序至关重要。本文将基于Python Mastery项目中的练习内容,深入探讨Python列表和字典的内存分配行为,以及如何通过自定义容器来优化内存使用。

列表的内存增长机制

Python列表的append()操作经过高度优化,其内存分配策略值得深入研究:

import sys
items = []
print(sys.getsizeof(items))  # 初始大小:64字节
items.append(1)
print(sys.getsizeof(items))  # 增长到96字节
items.append(2)
print(sys.getsizeof(items))  # 大小不变:96字节

关键观察点:

  1. 列表不是每次添加元素都重新分配内存
  2. Python会预先分配比当前需求更大的内存空间
  3. 当预留空间耗尽时,列表会进行较大幅度的扩容

技术细节:

  • 在64位系统上,每个列表元素引用占用8字节
  • 列表对象本身有固定大小的开销(存储长度、容量等信息)
  • 扩容策略通常是按比例增长(如每次增加约12.5%)

字典的内存特性

字典(及类)的内存分配行为与列表有所不同:

row = {'route': '22', 'date': '01/01/2001'}
print(sys.getsizeof(row))  # 初始大小:240字节
row['a'] = 1
print(sys.getsizeof(row))  # 大小不变
row['b'] = 2
print(sys.getsizeof(row))  # 增长到368字节

重要发现:

  1. 字典允许存储5个键值对后才进行内存翻倍
  2. 删除元素不会自动缩小内存占用
  3. 对于大量小型记录,字典可能不是最有效的选择

替代方案建议:

  • 使用元组或命名元组
  • 定义使用__slots__的类
  • 考虑使用数组或NumPy等专门数据结构

列式存储的内存优化

将行式数据转换为列式存储可以显著减少内存使用:

def read_rides_as_columns(filename):
    routes, dates, daytypes, numrides = [], [], [], []
    # 读取数据到四个独立的列表
    return {'routes': routes, 'dates': dates, 
            'daytypes': daytypes, 'numrides': numrides}

内存优势分析:

  1. 消除了每个记录单独字典的开销
  2. 仅需存储指向数据的指针(每个8字节)
  3. 对于577,563行数据,预计节省约120MB内存

自定义容器实现

为了保持原有接口同时获得内存优化,我们可以实现自定义容器:

from collections.abc import Sequence

class RideData(Sequence):
    def __init__(self):
        self.routes = []
        self.dates = []
        self.daytypes = []
        self.numrides = []
    
    def __len__(self):
        return len(self.routes)
    
    def __getitem__(self, index):
        return {
            'route': self.routes[index],
            'date': self.dates[index],
            'daytype': self.daytypes[index],
            'rides': self.numrides[index]
        }
    
    def append(self, d):
        self.routes.append(d['route'])
        self.dates.append(d['date'])
        self.daytypes.append(d['daytype'])
        self.numrides.append(d['rides'])

关键设计点:

  1. 继承collections.abc.Sequence确保序列行为
  2. 实现必需方法__len____getitem__
  3. 提供兼容的append方法
  4. 内部使用列式存储,外部表现为行式访问

切片功能增强

为了使自定义容器完全模拟列表行为,需要正确处理切片操作:

def __getitem__(self, index):
    if isinstance(index, slice):
        # 创建新的RideData实例来处理切片
        result = RideData()
        for i in range(*index.indices(len(self))):
            result.append(self[i])
        return result
    return { ... }  # 正常索引访问

切片实现要点:

  1. 检查索引类型是否为slice对象
  2. 使用slice.indices计算实际索引范围
  3. 创建新实例并填充切片数据
  4. 保持与原实例相同的接口和行为

性能考量

在实际应用中,这种设计需要在内存使用和访问速度之间权衡:

  1. 内存优势:

    • 消除了大量小字典的开销
    • 连续内存布局可能提高缓存命中率
  2. 访问成本:

    • 每次访问需要构建新字典
    • 大量随机访问可能不如原生列表高效
  3. 适用场景:

    • 数据量大的只读或低频修改场景
    • 需要保持原有接口的迁移场景
    • 内存受限的环境

总结

通过Python Mastery项目的这个练习,我们深入理解了:

  1. Python内置容器的内存分配行为
  2. 列式存储相对于行式存储的内存优势
  3. 如何通过自定义容器保持接口兼容性
  4. Python抽象基类在实现自定义容器中的作用

这种内存优化技术在大数据处理、科学计算等领域尤为重要,掌握这些底层知识可以帮助开发者编写出更高效的Python代码。

python-mastery Advanced Python Mastery (course by @dabeaz) python-mastery 项目地址: https://gitcode.com/gh_mirrors/py/python-mastery

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

经庄纲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值