Numpy数组与Python列表存储大小深度解析:优化与误区

numpy数组与python列表存储大小深度解析:优化与误区

本文深入探讨了Numpy数组在文件存储时可能比等效Python列表更大的原因,打破了Numpy总是更节省内存的普遍认知。核心在于Numpy的np.save默认存储原始二进制数据不进行压缩,而Python的pickle机制在遇到重复对象时会存储引用而非副本,从而在特定场景下导致文件大小差异。文章提供了使用numpy.savez_compressed进行Numpy数组压缩的解决方案,并揭示了Python列表通过显式复制来避免意外小文件大小的方法。

理解Numpy数组的存储机制

Numpy数组以其高效的内存使用和快速的数值计算能力而闻名。然而,这种效率主要体现在内存中的数据结构和计算操作上,而非默认的文件存储方式。当使用np.save函数保存Numpy数组时,它通常会以原始的二进制格式存储数据,不进行任何压缩。这意味着文件大小直接取决于数组的维度、数据类型(dtype)以及元素数量。

以一个10000 x 10000 x 7的np.float16类型数组为例:

数组维度:10000 * 10000 * 7数据类型:np.float16,每个元素占用2字节(16位)总大小计算:10000 * 10000 * 7 * 2 字节 = 1,400,000,000 字节 ≈ 1.4 GB

这个计算结果与实际观察到的1.4GB文件大小完全吻合。np.save保存的就是这种未经压缩的原始数据。

Python列表与Pickle的“引用”优化

与Numpy的原始数据存储不同,Python的pickle模块在序列化对象时,会尝试优化存储空间。当序列化一个包含多个对同一对象的引用的数据结构时,pickle不会重复存储这些对象的完整副本,而是只存储一份对象数据,并在其他地方存储对该对象的引用。

立即学习“Python免费学习笔记(深入)”;

在示例场景中,all_games是一个包含多个7元素浮点数列表的列表。当通过random.choice(all_games)来构建sampled_data时,如果all_games中的元素(即那些7元素列表)被重复选中,pickle会检测到这一点。它会为每个独特的7元素列表存储一次其内容,而对于后续出现的相同列表,则只存储一个指向已存储内容的引用。

考虑以下Python列表生成方式:

import randomimport pickleimport numpy as np# 假设 all_games 包含一些独特的7元素列表# 例如:all_games = [[float(i) for i in range(7)] for _ in range(100)]# 如果 all_games 元素数量远小于 SAMPLE * DRAW,则重复引用的可能性很高def sample_games_list_pickle(all_games, file_name='sampled_list.pkl'):    DRAW = 10000    SAMPLE = 10000    # 这里的 random.choice(all_games) 可能会重复选择 all_games 中的同一个子列表对象    sampled_data = [[random.choice(all_games) for _ in range(DRAW)] for _ in range(SAMPLE)]    with open(file_name, 'wb') as file:        pickle.dump(sampled_data, file)    print(f"Pickled list saved to {file_name}")# 示例调用 (需要先定义 all_games)# all_games_example = [[float(j) for j in range(7)] for _ in range(100)] # 假设只有100个独特的子列表# sample_games_list_pickle(all_games_example, 'sampled_list_optimized.pkl')

在这种情况下,如果all_games中只有少数几个独特的7元素列表被反复选中,pickle文件将远小于预期,因为它只存储了这些独特列表的内容以及大量的内部引用。这就是为什么500MB的pickle文件会比1.4GB的Numpy数组小得多的原因。

强制Python列表存储副本

为了验证pickle的这种引用优化行为,我们可以修改Python列表的生成方式,强制每次都存储一个独立的副本。通过在random.choice(all_games)的结果上调用.copy(),我们可以确保sampled_data中的每个7元素列表都是一个全新的对象,即使它们的内容可能相同。

import randomimport pickledef sample_games_list_pickle_with_copy(all_games, file_name='sampled_list_copied.pkl'):    DRAW = 10000    SAMPLE = 10000    # 显式复制子列表,强制 pickle 存储所有副本    sampled_data = [[random.choice(all_games).copy() for _ in range(DRAW)] for _ in range(SAMPLE)]    with open(file_name, 'wb') as file:        pickle.dump(sampled_data, file)    print(f"Pickled list with copies saved to {file_name}")# 示例调用 (需要先定义 all_games)# all_games_example = [[float(j) for j in range(7)] for _ in range(100)]# sample_games_list_pickle_with_copy(all_games_example, 'sampled_list_copied.pkl')

如果运行上述代码,你会发现生成的sampled_list_copied.pkl文件大小会急剧增加,可能达到4.4 GB左右。这证实了pickle在没有显式复制时,确实利用了对象引用来节省存储空间。

优化Numpy数组的存储大小

为了减小Numpy数组的文件大小,尤其是当数据量较大且需要存储到磁盘时,可以使用numpy.savez_compressed函数。这个函数会将Numpy数组保存为.npz格式,并使用ZIP压缩算法对数据进行压缩。

import numpy as npdef sample_games_numpy_compressed(all_games_list, file_name='sampled_data_compressed.npz'):    # 将原始Python列表转换为Numpy数组    all_games_np = np.array(all_games_list, dtype=np.float16)    DRAW = 10000    SAMPLE = 10000    rng = np.random.default_rng() # 推荐使用新的随机数生成器    # 生成随机索引    sampled_indices = rng.choice(all_games_np.shape[0], size=(SAMPLE, DRAW), replace=True)    # 根据索引获取采样数据    sampled_data = all_games_np[sampled_indices]    # 使用 savez_compressed 保存压缩后的Numpy数组    np.savez_compressed(file_name, data=sampled_data)    print(f"Compressed Numpy array saved to {file_name}")# 示例调用 (需要先定义 all_games_list)# all_games_list_example = [[float(j) for j in range(7)] for _ in range(100000)] # 假设有10万个独特的子列表# sample_games_numpy_compressed(all_games_list_example, 'sampled_data_compressed.npz')

通过使用np.savez_compressed,生成的.npz文件大小通常会显著小于原始的1.4GB,甚至可能小于500MB的pickle文件,具体取决于数据的可压缩性。

总结与注意事项

Numpy np.save: 默认存储原始二进制数据,不进行压缩。文件大小直接反映数据量和数据类型。Python pickle.dump: 会尝试优化存储,对重复的对象存储引用而非副本。这可能导致文件意外地小,但如果所有对象都是独立的,文件大小会非常大。优化Numpy存储: 使用numpy.savez_compressed是减小Numpy数组文件大小的有效方法。Python列表陷阱: 如果你希望pickle存储所有对象的独立副本(例如,为了避免后续修改一个对象影响到其他“引用”它的地方),请确保在构建列表时显式地进行.copy()操作。选择存储格式:对于需要高性能计算且数据在内存中处理时,Numpy数组是首选。对于磁盘存储,如果数据量大,Numpy的savez_compressed或HDF5等格式更为合适。pickle适用于序列化任意Python对象,但其文件大小和性能可能不如专门的数据格式。在任何情况下,都应根据实际需求(内存、磁盘、读写速度、互操作性等)权衡选择最合适的存储方案。

理解不同数据结构和序列化方法的底层机制,对于高效地处理和存储数据至关重要。

以上就是Numpy数组与Python列表存储大小深度解析:优化与误区的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1375699.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 15:14:50
下一篇 2025年12月14日 15:15:07

相关推荐

  • Python3集合怎么使用_Python3集合的定义与常用操作详解

    Python3集合是无序不重复元素集,支持去重和集合运算。可用{}或set()创建非空集合,空集合需用set();add()添加单个元素,update()添加多个元素,remove()、discard()、pop()用于删除。支持并集(|)、交集(&)、差集(-)、对称差集(^)等数学运算;…

    2025年12月14日
    000
  • pythonfor循环怎样实现对元组求和_pythonfor循环对元组内元素求和的详细步骤

    答案:通过for循环遍历元组元素并累加求和。首先定义元组tup = (10, 20, 30, 40),初始化total = 0,然后用for循环依次将每个元素加到total上,最后输出结果为100。 在 Python 中,可以使用 for 循环遍历元组中的每个元素,并通过累加的方式实现求和。下面详细…

    2025年12月14日
    000
  • Python多线程如何实现条件变量 Python多线程复杂同步机制详解

    条件变量用于协调多线程执行,解决互斥锁无法处理的等待与通知问题。它结合锁和等待队列,支持线程在条件不满足时挂起并由其他线程唤醒,适用于生产者-消费者等场景。通过 threading.Condition 实现,推荐使用 with 语句管理锁,调用 wait() 前需持有锁,且应使用 while 循环检…

    2025年12月14日
    000
  • Python代码如何连接MySQL数据库 Python代码使用PyMySQL驱动的连接方法

    答案:PyMySQL是纯Python实现的MySQL驱动,安装简单、跨平台兼容性好,支持参数化查询和DictCursor返回字典结果,避免SQL注入并提升代码可读性;实际项目中应通过环境变量或配置文件管理数据库凭证以确保安全,并使用DBUtils等工具构建连接池提升高并发场景下的性能;处理大数据量时…

    2025年12月14日
    000
  • Python3包怎么创建_Python3包的创建与导入使用详细指南

    答案:创建Python包需在目录中添加__init__.py文件,通过setup.py安装后可导入使用。具体步骤包括:建立包结构,配置__init__.py控制导入行为,使用相对导入模块,通过setuptools安装包,最后验证导入功能。 如果您尝试在Python3中组织代码,但模块无法被正确识别或…

    2025年12月14日
    000
  • pyO3中从Rust检查Python自定义类实例类型的方法

    本文旨在解决在rust中使用pyo3库时,如何准确判断一个`pyany`对象是否为python中定义的自定义类实例的问题。针对用户在尝试使用`pytypeinfo`时遇到的困惑,文章将介绍一种更简洁、安全且推荐的方法:通过动态获取python类类型对象,并结合`pyany::is_instance(…

    2025年12月14日
    000
  • Openpyxl与Pytest:正确判断Excel空单元格的策略

    在使用openpyxl和pytest测试excel单元格是否为空时,直接断言`is none`可能因单元格实际为`””`(空字符串)而失败。本文将详细阐述这一常见问题,并提供一个健壮的解决方案,通过同时检查`none`和`””`来确保准确判断空单元格,…

    2025年12月14日
    000
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2025年12月14日
    000
  • python模块的搜索路径和顺序

    Python导入模块时按顺序搜索路径:先当前脚本目录,再PYTHONPATH环境变量指定的目录,最后是安装默认路径如标准库和site-packages。可通过sys.path查看当前搜索路径列表,其顺序决定模块查找优先级。使用sys.path.insert(0, ‘path’…

    2025年12月14日
    000
  • Python3官网官方网址是什么样的_Python3官方网址样式与功能介绍

    Python3官网官方网址是https://www.python.org/,采用极简风格设计,顶部导航栏包含Downloads、Documentation、About、Community等核心栏目,首页突出显示最新稳定版本及下载按钮,底部提供PEP索引、第三方模块仓库、开发进度报告和多语言社区资源链…

    2025年12月14日
    000
  • Python多个版本环境变量怎么配置_多版本Python环境变量设置与管理方法

    合理配置环境变量可在Windows中管理多个Python版本:1. 为不同版本设置独立安装路径并手动添加至Path;2. 路径顺序决定默认版本优先级;3. 推荐使用py -X.Y命令通过Python启动器切换版本;4. 为项目创建虚拟环境以隔离依赖,避免冲突。手动管理PATH、结合py启动器与虚拟环…

    2025年12月14日
    000
  • Python有哪些命令行参数解析模块?

    推荐使用argparse解析命令行参数,它功能完整且用户友好,支持位置与可选参数、子命令、类型检查及自动生成帮助;getopt适用于简单场景或旧代码兼容;optparse已弃用;第三方库click采用装饰器风格,适合复杂CLI应用;fire由Google开发,可快速将函数或类转为命令行接口,适合原型…

    2025年12月14日
    000
  • Python入门如何操作文件读写_Python入门文件处理的标准操作

    掌握Python文件读写需使用open()函数并合理选择模式,推荐with语句自动管理文件生命周期,逐行读取大文件以节省内存,写入时注意模式与编码,统一使用UTF-8处理中文字符。 如果您需要在Python中处理文件,例如读取配置、保存数据或生成报告,掌握文件的读写操作是必不可少的基础技能。以下是P…

    2025年12月14日
    000
  • python多进程与多线程的简单区分

    多进程适合CPU密集型任务,利用多核并行计算,如数值处理;多线程适合I/O密集型任务,轻量高效,如网络请求。 Python中多进程和多线程都是实现并发的方式,但它们的使用场景和底层机制有明显区别。理解这些差异有助于在实际开发中做出合适选择。 多进程(multiprocessing) 每个进程拥有独立…

    2025年12月14日
    000
  • python中geth如何使用?

    答案:Python通过web3.py库连接启用RPC的Geth节点实现交互。首先启动Geth并开启HTTP-RPC服务,配置允许的API模块;接着安装web3.py库,使用Web3.HTTPProvider连接本地8545端口;成功后可获取账户、查询余额、发送交易、调用合约等;注意安全设置与网络选择…

    2025年12月14日
    000
  • Python官网Debug技巧的全面掌握_Python官网调试工具使用教程

    首先使用pdb模块设置断点进行本地调试,再通过IDE集成工具实现图形化调试,结合logging记录执行信息,并利用debugpy实现远程调试。 如果您在使用Python官网提供的工具进行代码调试时遇到问题,可能是因为未正确配置调试环境或未掌握核心调试技巧。以下是帮助您全面掌握Python官方调试工具…

    2025年12月14日
    000
  • Python异步中loop抛出异常的解决

    事件循环异常主因是生命周期管理不当和未捕获错误。1. 避免在子线程直接调用get_event_loop(),应使用asyncio.run()自动管理;2. 协程内需用try/except处理异常,gather设return_exceptions=True防中断;3. 禁止重复运行或过早关闭循环,确保…

    2025年12月14日
    000
  • Python入门如何连接数据库_Python入门数据库操作的基本流程

    首先安装对应数据库的驱动模块,然后使用正确参数建立连接并获取游标,通过游标执行SQL语句实现增删改查,操作完成后提交事务并关闭游标与连接以释放资源。 如果您希望在Python程序中对数据库进行增删改查操作,但不知道如何建立连接并执行基本指令,这通常是因为尚未配置好数据库驱动或连接参数。以下是实现Py…

    2025年12月14日
    000
  • python进程池的使用注意

    答案:使用Python进程池需在if name == ‘__main__’:中创建,合理设置进程数,及时关闭并回收资源,避免传递不可序列化的对象。 使用Python进程池时,关键在于合理管理资源和避免常见陷阱。进程池适合处理CPU密集型任务,但若使用不当,可能导致性能下降甚至…

    2025年12月14日
    000
  • python在函数中传递实参

    Python函数传参方式包括位置实参、关键字实参、默认参数值及args和kwargs。位置实参按顺序传递,关键字实参通过“形参名=实参”指定,提高可读性;默认参数在定义时赋初值,简化调用;args收集多余位置参数为元组,kwargs收集关键字参数为字典,使函数支持可变数量输入,提升灵活性与通用性。 …

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信