pin_drop当前位置:知识文库 ❯ 图文
Python sys.getsizeof()详解 - 内存占用测量与优化完整指南
概述
sys.getsizeof() 是 Python 标准库中用于测量对象内存占用大小的函数。它返回对象在内存中占用的字节数,包括对象头部信息和基本属性,但不包括对象引用的其他对象的大小。sys.getsizeof() 是性能分析和内存优化的基础工具,帮助开发者了解不同数据结构的内存开销,从而做出更合理的设计选择。
语法
代码示例
import sys
sys.getsizeof(object[, default])参数说明
返回值
返回 int,表示对象占用的内存字节数。如果对象无法提供大小且未指定 default,则引发 TypeError 异常。
代码示例
示例1:基本数据类型的内存占用
代码示例
import sys
# 数值类型
print("数值类型:")
print(f" int(0): {sys.getsizeof(0)} 字节")
print(f" int(1): {sys.getsizeof(1)} 字节")
print(f" int(10**6): {sys.getsizeof(10**6)} 字节")
print(f" float(1.0): {sys.getsizeof(1.0)} 字节")
print(f" bool(True): {sys.getsizeof(True)} 字节")
print(f" None: {sys.getsizeof(None)} 字节")
# 字符串类型
print("\n字符串类型:")
print(f" '': {sys.getsizeof('')} 字节")
print(f" 'a': {sys.getsizeof('a')} 字节")
print(f" 'abc': {sys.getsizeof('abc')} 字节")
print(f" '你好': {sys.getsizeof('你好')} 字节")
# 容器类型(仅计算容器本身,不含元素内容)
print("\n容器类型(空容器):")
print(f" []: {sys.getsizeof([])} 字节")
print(f" (): {sys.getsizeof(())} 字节")
print(f" {{}}: {sys.getsizeof({})} 字节")
print(f" set(): {sys.getsizeof(set())} 字节")输出:
代码示例
数值类型:
int(0): 24 字节
int(1): 28 字节
int(10**6): 28 字节
float(1.0): 24 字节
bool(True): 28 字节
None: 16 字节
字符串类型:
'': 49 字节
'a': 50 字节
'abc': 52 字节
'你好': 53 字节
容器类型(空容器):
[]: 56 字节
(): 40 字节
{}: 64 字节
set(): 216 字节示例2:容器增长时的内存变化
代码示例
import sys
def track_list_growth():
"""跟踪列表增长时的内存变化"""
lst = []
sizes = []
for i in range(20):
size = sys.getsizeof(lst)
sizes.append((len(lst), size))
lst.append(i)
print("列表长度 -> 内存大小 (字节)")
print("-" * 35)
for length, size in sizes:
bar = '#' * (size // 10)
print(f" {length:2d} -> {size:4d} {bar}")
track_list_growth()输出:
代码示例
列表长度 -> 内存大小 (字节)
-----------------------------------
0 -> 56
1 -> 88 ########
2 -> 88 ########
3 -> 88 ########
4 -> 88 ########
5 -> 120 ###########
6 -> 120 ###########
7 -> 120 ###########
8 -> 120 ###########
9 -> 184 ##################
10 -> 184 ##################
...示例3:递归计算对象总内存
代码示例
import sys
from collections.abc import Mapping, Iterable
def get_total_size(obj, seen=None):
"""递归计算对象及其引用的所有对象的总内存大小"""
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
seen.add(obj_id)
size = sys.getsizeof(obj)
# 字符串和字节类型不再递归
if isinstance(obj, (str, bytes, bytearray)):
return size
# 字典类型
if isinstance(obj, Mapping):
size += sum(get_total_size(k, seen) + get_total_size(v, seen)
for k, v in obj.items())
# 可迭代类型(排除字符串)
elif isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)):
size += sum(get_total_size(item, seen) for item in obj)
return size
# 对比浅层和深层内存占用
data = {
"name": "张三",
"scores": [85, 92, 78, 95, 88],
"info": {"age": 25, "city": "北京"}
}
shallow = sys.getsizeof(data)
deep = get_total_size(data)
print(f"浅层内存占用: {shallow} 字节")
print(f"深层内存占用: {deep} 字节")
print(f"差异: {deep - shallow} 字节 (被引用对象)")输出:
代码示例
浅层内存占用: 184 字节
深层内存占用: 856 字节
差异: 672 字节 (被引用对象)实际应用场景
-
内存优化:比较不同数据结构(如列表 vs 元组、字典 vs 命名元组)的内存开销,选择更节省内存的方案
-
性能分析:定位内存占用过大的对象,优化数据存储方式
-
缓存策略:评估缓存对象的内存成本,合理设置缓存上限
注意事项
注意1:
sys.getsizeof()只计算对象本身的内存,不包括其引用的其他对象。例如列表的 size 不包含列表中元素的大小。要计算总内存需要递归遍历。
注意2:不同 Python 版本和实现(CPython、PyPy 等)中,相同对象的内存大小可能不同。以上示例基于 CPython 3.11。
注意3:Python 中小整数(-5 到 256)和小字符串会被缓存(intern),多次引用同一对象不会额外占用内存。
注意4:容器类型(如列表、字典)在添加元素时会预分配额外空间,因此内存增长不是线性的,而是阶梯式的。
提示:对于更深入的内存分析,推荐使用
tracemalloc模块或第三方工具memory_profiler、pympler,它们能提供更详细的内存分配追踪。
相关方法对比
小结
-
sys.getsizeof()是测量单个对象内存占用的快捷方法,返回浅层字节数 -
不同数据类型的内存开销差异显著,选择合适的数据结构能有效节省内存
-
容器类型存在预分配机制,内存增长呈阶梯式而非线性
-
需要计算对象总内存时,需自行实现递归遍历或使用
pympler等第三方库
练习题
练习1
编写一个函数,比较空列表、空元组、空字典、空集合的内存占用,并计算存储 1000 个整数时各容器的总内存差异。
练习2
编写一个递归函数 deep_getsizeof(),能够计算嵌套数据结构(如嵌套字典、嵌套列表)的总内存占用,并处理循环引用问题。
练习3
编写一个内存分析工具,接受一个对象,输出其浅层内存、深层内存、以及各子对象的内存占比排名。
常见问题
sys.getsizeof() 计算的是对象的总内存吗?
不是。sys.getsizeof() 只计算对象本身的内存(浅层大小),不包括其引用的其他对象。例如列表的大小不包含列表中元素的大小。要计算总内存需要递归遍历或使用 pympler.asizeof()。
为什么列表的内存增长是阶梯式的?
Python 列表在添加元素时会预分配额外空间,以减少频繁内存分配的开销。当预分配空间用完后,会一次性分配更大的空间,因此内存增长呈阶梯式而非线性。
如何计算嵌套对象的总内存?
需要自行实现递归遍历函数,遍历对象及其引用的所有子对象,累加每个对象的 sys.getsizeof() 值。注意要处理循环引用问题,使用 seen 集合记录已访问的对象 ID。
列表和元组的内存占用有什么区别?
元组的内存占用通常比列表小,因为元组是不可变的,不需要预分配额外空间。列表为了支持动态增长,会预分配额外容量。对于固定大小的数据集合,使用元组更节省内存。
本文涉及AI创作
内容由AI创作,请仔细甄别