暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

python 内存系列(8)-内存泄漏的几个库的介绍

小儿来一壶枸杞酒泡茶 2021-03-09
3625

前言

纯属个人实践中相关经验之谈,如有纰漏,还希望大佬们多多提点!

小钟同学 | 文  【原创】| QQ:308711822


内存泄漏一直是我们的编写程序代码中遇到的最蛋疼的事,而且有些时候有些泄漏是隐匿的。对于服务端的程序来说,如果存在内存泄漏的会,会引发我们的系统内存资源不足,从而引发一系列的故障。

所以内存泄漏的定位也是非常关键一步,之前我们的已经有介绍了关于python内置模块GC一些方法,不过在对于一些循环引用对象的关系中,似乎还是无法可以得到明确的对象之间相关关系链。

一、标准库trancemalloc 3.4+以上

tracemalloc是用来分析Python程序内存分配的工具,是一个集成到内置库的工具。如果单纯是希望了解内存占用大小的话,使用它是一个不错的选择。

如:获取记录所有跟踪内存块的当前大小和峰值大小

import tracemalloc
import objgraph
import gc


class A():
    pass

    def __del__(self):
        print('Dangerous!')


def funcext():
    a = A()
    b = A()
    a.chifan = b
    b.chifan = A

    del a
    del b

    print('垃圾回收哦', gc.collect())


if __name__ == '__main__':

    tracemalloc.start(25)  # 默认25个片段,这个本质还是多次采样
    funcext()  # 这是我自己定义的对象成员函数
    # 记录所有跟踪内存块的当前大小和峰值大小
    size, peak = tracemalloc.get_traced_memory()
    # 一个输出到控制台中,一个输出到文件
    print('memory blocks:{:>10.4f} KiB'.format(peak / 1024))


输出的结果是:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/ceshgf.py
Dangerous!
Dangerous!
垃圾回收哦 0
memory blocks:    0.4609 KiB

官方示例:


import tracemalloc  # from 3.4

tracemalloc.start()  # 开始跟踪内存分配

d = [dict(zip('xy', (5, 6))) for i in range(1002)]
t = [tuple(zip('xy', (5, 6))) for i in range(10003)]

snapshot = tracemalloc.take_snapshot()  # 快照,当前内存分配
top_stats = snapshot.statistics('lineno')  # 快照对象的统计


d = [dict(zip('xy', (5, 6))) for i in range(100223)]
t = [tuple(zip('xy', (5, 6))) for i in range(100033)]

snapshot = tracemalloc.take_snapshot()  # 快照,当前内存分配
top_stats = snapshot.statistics('lineno')  # 快照对象的统计


for stat in top_stats:
    print(stat)


输出的结果为:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/trancemallocshi.py
d:/code/vscode/py/trancemallocshi.py:23: size=14.5 MiB, count=200450, average=76 B
d:/code/vscode/py/trancemallocshi.py:24: size=10.7 MiB, count=300100, average=37 B
d:/code/vscode/py/trancemallocshi.py:17: size=70.3 KiB, count=2000, average=36 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:460: size=468 B, count=1, average=468 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:432: size=432 B, count=3, average=144 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:425: size=408 B, count=3, average=136 B
d:/code/vscode/py/trancemallocshi.py:19: size=400 B, count=1, average=400 B
d:/code/vscode/py/trancemallocshi.py:20: size=376 B, count=1, average=376 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:462: size=352 B, count=1, average=352 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:428: size=348 B, count=1, average=348 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:349: size=84 B, count=3, average=28 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:461: size=56 B, count=2, average=28 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:429: size=36 B, count=2, average=18 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:487: size=32 B, count=1, average=32 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:430: size=28 B, count=2, average=14 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:277: size=16 B, count=1, average=16 B
(.venv) PS D:\code\vscode\py>

两个的快照的对比示例:

import random
import tracemalloc  # from 3.4

tracemalloc.start()  # 开始跟踪内存分配


class A():
    def __init__(self):
        super().__init__()

    def randomlist(self, n):
        lists = []
        l = [random.random() for i in range(n)]
        l.sort()
        for v in l:
            lists.append(v)
        return lists

    def randomlist2(self, n):
        lists = []
        l = [random.random() for i in range(n)]
        l.sort()
        for v in l:
            lists.append(v)
        return lists


A().randomlist(20)

snapshot1 = tracemalloc.take_snapshot()  # 快照,当前内存分配

A().randomlist(20000)

snapshot2 = tracemalloc.take_snapshot()  # 快照,当前内存分配


# 对比两个内存的快照
top_stats = snapshot2.compare_to(snapshot1, 'lineno')

# top_stats = snapshot.statistics('lineno')  # 快照对象的统计

for stat in top_stats[:10]:
    print(stat)


输出的结果:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/neicuntongji.py
d:/code/vscode/py/neicuntongji.py:24: size=1600 B (+1360 B), count=100 (+85), average=16 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:349: size=468 B (+468 B), count=5 (+5), average=94 B 
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:487: size=424 B (+424 B), count=3 (+3), average=141 B
d:/code/vscode/py/neicuntongji.py:41: size=400 B (+400 B), count=1 (+1), average=400 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:275: size=32 B (+32 B), count=1 (+1), average=32 B   
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\tracemalloc.py:277: size=16 B (+16 B), count=1 (+1), average=16 B   
d:/code/vscode/py/neicuntongji.py:18: size=908 B (+0 B), count=8 (+0), average=114 B
d:/code/vscode/py/neicuntongji.py:30: size=72 B (+0 B), count=1 (+0), average=72 B
d:/code/vscode/py/neicuntongji.py:22: size=72 B (+0 B), count=1 (+0), average=72 B
d:/code/vscode/py/neicuntongji.py:19: size=72 B (+0 B), count=1 (+0), average=72 B
(.venv) PS D:\code\vscode\py> 


引入到我们的Flask框架中进行分析:

'''
Author: 小钟同学
objectDescription: 项目描述
Date: 2021-03-07 21:48:40
LastEditors: 308711822@qq.com
LastEditTime: 2021-03-09 17:06:57
FilePath: \py\flaskmain.py
Version: 1.0
'
''
from flask import Flask, jsonify
import time


import tracemalloc


app = Flask(__name__)


@app.route('/')
def line_test():

    for item in range(5):
        print(item)
        time.sleep(0.2)

    snapshot2 = tracemalloc.take_snapshot()
    top_stats = snapshot2.compare_to(snapshot1, 'lineno')
    for stat in top_stats[:10]:
        print(stat)

    return jsonify({'code': 200})


if __name__ == '__main__':
    tracemalloc.start()
    snapshot1 = tracemalloc.take_snapshot()
    app.run()


请求访问我们的接口地址:http://127.0.0.1:5000/ 查看输出结果:

0
1
2
3
4
<frozen importlib._bootstrap_external>:508: size=29.4 KiB (+29.4 KiB), count=1082 (+1082), average=28 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\socket.py:240: size=16.7 KiB (+16.7 KiB), count=7 (+7), average=2439 B     
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\stringprep.py:187: size=12.1 KiB (+12.1 KiB), count=2 (+2), average=6170 B 
D:\code\vscode\py\.venv\lib\site-packages\click\_winconsole.py:282: size=9092 B (+9092 B), count=6 (+6), average=1515 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\stringprep.py:262: size=5784 B (+5784 B), count=99 (+99), average=58 B     
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\encodings\idna.py:292: size=1896 B (+1896 B), count=11 (+11), average=172 B
D:\code\vscode\py\.venv\lib\site-packages\click\_compat.py:71: size=1694 B (+1694 B), count=9 (+9), average=188 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\stringprep.py:220: size=1616 B (+1616 B), count=28 (+28), average=58 B     
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\stringprep.py:19: size=1432 B (+1432 B), count=19 (+19), average=75 B
C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\encodings\utf_16_le.py:18: size=1392 B (+1392 B), count=9 (+9), average=155 B
127.0.0.1 - - [09/Mar/2021 17:07:11] "GET / HTTP/1.1" 200 -

二 第三方库-objgraph

本节我自己尝试引入objgraph实践一下,objgraph它可以很好帮助我们的进行分析对象之前的引用关系,就是所谓对象关系图。借助于xdot 还可以进行可视化对象关系。

官网地址:

https://mg.pov.lt/objgraph/

1. objgraph安装

pip install objgrap

可视化依赖库xdot:

官网地址:https://github.com/jrfonseca/xdot.py

pip install xdot

windows上运行的时候提示缺少gi模块:但是安装这个gi模块也无法安装!

绘图依赖:

pip install  graphviz

2、官网示例应用实践

2.1 引用对象图

  • c_profile_text_run.py

import objgraph
x = []
y = [x, [x], dict(x=x)]
# 分析我们的Y列表对象生产它的对象关系引用图
objgraph.show_refs([y], filename='sample-graph.png')
# Graph written to ....dot (... nodes)
# Image generated as sample-graph.png


然后右键运行或命令运行我们的上面的py文件:

(.venv) PS D:\code\vscode\py> python .\c_profile_text_run.py
Graph written to C:\Users\mayn\AppData\Local\Temp\objgraph-y03ago35.dot (4 nodes)
Image generated as sample-graph.png
(.venv) PS D:\code\vscode\py> 

查看生成的图片文件sample-graph.png:

从图我们我可以看到Y 里面有三个对象,

  • 一个是字典类型的对象
  • 一个只列表类似的对象
  • 指向X对象的对象

使用xdot进行可视化数据分析(不合适windows上运行)

xdot.py可以用作命令中的独立应用程序 行,或作为嵌入到python应用程序中的库。如果想使用xdot进行可视化数据分析,去除生成的图片文件参数名即可:

# 性能分析装饰器定义
import objgraph
x = []
y = [x, [x], dict(x=x)]
objgraph.show_refs([y])

然后执行我们的分析:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/c_profile_text_run.py
Graph written to C:\Users\mayn\AppData\Local\Temp\objgraph-97c1zm6s.dot (4 nodes)
Spawning graph viewer (xdot)
(.venv) PS D:\code\vscode\py> 

生产了一个临时的文件C:\Users\mayn\AppData\Local\Temp\objgraph-97c1zm6s.dot

2.2 反向引用对象图


# 性能分析装饰器定义
import objgraph
# import pdb
# pdb.set_trace()
x = []
y = [x, [x], dict(x=x)]
objgraph.show_backrefs([x], filename='sample-backref-graph.png')
#objgraph.show_backrefs(objgraph.by_type('OBJ')[0], max_depth = 10, filename = 'obj.dot')

PS:max_depth一般建议设置一下,大小要适宜,太小,看不到完整的引用链,太大,运行耗时长。

2.3 常用几个方法:

  • 快速了解内存中的对象,查看最常用的类型,limit限制查看最前面的多少个。

    objgraph.show_most_common_types(limit=20)

  • 查看增长最快的类型,通过进行快照对象对比来分析。

    objgraph.show_growth(limit=10)

    使用场景:间隔某些时间段调用这方法,查看哪些对象类型增加比较快。t它统计自上次调用以来增加得最多的对象,这个函数非常有利于发现潜在的内存泄露。函数内部调用了gc.collect(),因此即使有循环引用也不会对判断造成影响。

objgraph.show_growth使用示例1:

import objgraph
x = []
y = [x, [x], dict(x=x)]

objgraph.show_growth(limit=10)
print("===========")
y23 = [x, [x], dict(x=x)]
del x
objgraph.show_growth(limit=10)
print("===========")


输出结果:

function                       2215     +2215
dict                           1169     +1169
wrapper_descriptor             1020     +1020
tuple                           950      +950
weakref                         849      +849
method_descriptor               721      +721
builtin_function_or_method      656      +656
getset_descriptor               387      +387
set                             384      +384
list                            338      +338
===========
list      340        +2
dict     1170        +1
===========
(.venv) PS D:\code\vscode\py> 


objgraph.show_growth使用示例2:



import os

import gc

import objgraph

# 开始强制进行垃圾回收
gc.collect()

print('第1次调用show_growth时,上打印出来的是当前所有对象的总数====================================')
# 第一次统计我们的对象使用情况
objgraph.show_growth()
# 新建一个列表的对象
xiaozhong = []
print('第2次调用show_growth====================================')
# 对所有活着的对象进行快照
objgraph.show_growth()
# 向列表里面添加数据对象
xiaozhong.append(["sdasda""sfsdfdf", 3, {"还是的还是兑换"}])

print('第3次调用show_growth====================================')
# 对所有活着的对象进行快照
objgraph.show_growth()
# 新建一个列表
k = {"sdas": 3}
b = ['a''b''c']
# 对所有活着的对象进行快照
print('第4次调用show_growth====================================')
objgraph.show_growth()
del b

# 对所有活着的对象进行快照
del k
print('第5次调用show_growth====================================')
objgraph.show_growth()




输出结果为:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/xielou2.py
第1次调用show_growth时,上打印出来的是当前所有对象的总数====================================
function                       2215     +2215
dict                           1168     +1168
wrapper_descriptor             1020     +1020
tuple                           950      +950
weakref                         849      +849
method_descriptor               721      +721
builtin_function_or_method      656      +656
getset_descriptor               387      +387
set                             384      +384
list                            335      +335
第2次调用show_growth====================================
list      336        +1 (因为xiaozhong = [])
第3次调用show_growth====================================
list      337        +1 (因为xiaozhong.append(["sdasda""sfsdfdf", 3, {"还是的还是兑换"}]))
set       385        +1  (因为{"还是的还是兑换"})
第4次调用show_growth====================================
list      338        +1 因为(b = ['a''b''c'])
第5次调用show_growth====================================
(.venv) PS D:\code\vscode\py> 

  • 查看某个类型对象

    objgraph.by_type('list')

    使用场景:间隔某些时间段调用这方法,查看哪些对象类型增加比较快。

  • 查看垃圾回收器的个数

    objgraph.count()

    使用场景:间隔某些时间段调用这方法,查看垃圾回收的情况。

    本质其实就是通过gc.get_objects()拿到所用的对象,然后统计指定类型的数目。

示例如:

import objgraph


class PAPA(object):
    pass


class MAMA(object):
    pass


# 创建两个对象
baba = PAPA()
mama = MAMA()
# 查看当前垃圾回收期的个数
print("PAPA的垃圾个数有", objgraph.count('PAPA'))
print("MAMA的垃圾个数有", objgraph.count('MAMA'))


print("=========================")
del baba
del mama
print("PAPA的垃圾个数有", objgraph.count('PAPA'))
print("MAMA的垃圾个数有", objgraph.count('MAMA'))

输出结果:

PAPA的垃圾个数有 1
MAMA的垃圾个数有 1
=========================
PAPA的垃圾个数有 0
MAMA的垃圾个数有 0

引入循环引用的示例:

import objgraph


class PAPA(object):
    pass


class MAMA(object):
    pass


# 创建两个对象
baba = PAPA()
mama = MAMA()
# 查看当前垃圾回收期的个数
print("PAPA的垃圾个数有", objgraph.count('PAPA'))
print("MAMA的垃圾个数有", objgraph.count('MAMA'))

# 引入循环引用的示例
baba.laopo = mama
mama.laogong = baba
print("=========================")
del baba
del mama
print("PAPA的垃圾个数有", objgraph.count('PAPA'))
print("MAMA的垃圾个数有", objgraph.count('MAMA'))



输出结果为:

PAPA的垃圾个数有 1
MAMA的垃圾个数有 1
=========================
PAPA的垃圾个数有 1
MAMA的垃圾个数有 1
1

  • 生产一张有关objs的引用图,[反向引用对象图],

    objgraph.show_backrefs

    通过引用我们可以分析对象为什么不释放,后面会利用这个API来查内存泄露。

  • 找到一条指向obj对象的最短路径,且路径的头部节点需要满足predicate函数 (返回值为True)

    objgraph.find_backref_chain(obj, predicate, max_depth=20, extra_ignore=())

    可以快捷、清晰指出 对象的被引用的情况

3. 官方内存泄漏案例

不会产生泄漏示例:
import random
import objgraph


class MyBigFatObject(object):
    pass


def computate_something(_cache={}):
    x = MyBigFatObject()  # 不会产生内泄漏
    sdfd333 = MyBigFatObject()  # this one doesn't leak


# 在调用函数之前,我们对所有活着的对象进行快照
objgraph.show_growth(limit=3)
# 开始调用函数
computate_something()
# 再执行一次快照建立
objgraph.show_growth()

输出结果:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/c_profile_text_run.py
function               2216     +2216
dict                   1169     +1169
wrapper_descriptor     1020     +1020
(.venv) PS D:\code\vscode\py> 

产生内存泄漏示例:
import random
import objgraph


class MyBigFatObject(object):
    pass


def computate_something(_cache={}):
    # 很明细的产生的泄漏的地址
    _cache[42] = dict(foo=MyBigFatObject(),
                      bar=MyBigFatObject())
  
    x = MyBigFatObject()  # 不会产生内泄漏
    sdfd333 = MyBigFatObject() # 不会产生内泄漏




# 在调用函数之前,我们对所有活着的对象进行快照
objgraph.show_growth(limit=3)
# 开始调用函数
computate_something()
# 再执行一次快照建立
objgraph.show_growth()


输出结果:

(.venv) PS D:\code\vscode\py> & d:/code/vscode/py/.venv/Scripts/python.exe d:/code/vscode/py/c_profile_text_run.py
function               2216     +2216
dict                   1169     +1169
wrapper_descriptor     1020     +1020
MyBigFatObject        2        +2
dict               1171        +2
(.venv) PS D:\code\vscode\py> 

最明显的地方就是:

MyBigFatObject        2        +2
dict               1171        +2

追踪MyBigFatObject在垃圾回收器的情况:完整代码:

import random
import objgraph


class MyBigFatObject(object):
    pass


def computate_something(_cache={}):
    # 很明细的产生的泄漏的地址
    _cache[42] = dict(foo=MyBigFatObject(),
                      bar=MyBigFatObject())

    x = MyBigFatObject()  # 不会产生内泄漏
    sdfd333 = MyBigFatObject()  # 不会产生内泄漏


# 在调用函数之前,我们对所有活着的对象进行快照
objgraph.show_growth(limit=3)
# 开始调用函数
computate_something()
# 再执行一次快照建立
objgraph.show_growth()


# 显示所有的根模块示例
objgraph.show_chain(
    objgraph.find_backref_chain(
        random.choice(objgraph.by_type('MyBigFatObject')),
        objgraph.is_proper_module),
    filename='chain.png')


运行脚本结果输出查看相关的图片chain.png:

从上图看到我们的

是产生内存泄漏的对象(不可达对象),

上图说明的是:

  1. __main__模块下包含有了dict对象
  2. 里面包含you了computate_something
  3. computate_something里面创建了一个元组
  4. computate_something里面创建了一个元组里有存在两个字典

非官方示例分析步骤

参考地址:https://blog.csdn.net/ybn6775/article/details/81676801 泄漏示例:

1. 分析第1步:定位泄漏对象

import objgraph

_cache = []


class A():
   pass


def funcext():
   a = A()
   _cache.append(a)

   if True:
       return

   _cache.remove(a)


if __name__ == '__main__':
   # 第一步定位泄露的对象
   objgraph.show_growth()

   try:
       funcext()
   except:
       pass

   print("调用函数后")
   objgraph.show_growth()
  


2. 分析对象引用链

import objgraph

_cache = []


class A():
 pass


def funcext():
 a = A()
 _cache.append(a)

 if True:
     return

 _cache.remove(a)


if __name__ == '__main__':
 # 第1步定位泄露的对象
 objgraph.show_growth()

 try:
     funcext()
 except:
     pass

 print("")
 objgraph.show_growth()
 # 第2:定位了哪个对象发生了内存泄露,那么接下来就是分析怎么泄露的,引用链是怎么样的
 objgraph.show_backrefs(objgraph.by_type(
     'A')[0], max_depth=10, filename='obj.png')
 # objgraph.show_refs([y], filename='sample-graph.png')
 objgraph.show_backrefs(objgraph.by_type(
     'A')[0], max_depth=10, filename='obj.png')


查看我们的图:

可以看到泄露的对象(红框表示),是被一个叫_cache的list所引用,而_cache又是被__main__这个module所引用。

使用show_chain找到最短路径:

import objgraph

_cache = []


class A():
    pass


def funcext():
    a = A()
    _cache.append(a)

    if True:
        return

    _cache.remove(a)


if __name__ == '__main__':
    # 第一步定位泄露的对象
    objgraph.show_growth()

    try:
        funcext()
    except:
        pass

    print("")
    objgraph.show_growth()
    # 第二部:定位了哪个对象发生了内存泄露,那么接下来就是分析怎么泄露的,引用链是怎么样的
    objgraph.show_backrefs(objgraph.by_type(
        'A')[0], max_depth=10, filename='obj.png')
    # objgraph.show_refs([y], filename='sample-graph.png')
    # 第三部最短的路径
    import random
    objgraph.show_chain(
        objgraph.find_backref_chain(
            random.choice(objgraph.by_type('A')),
            objgraph.is_proper_module),
        filename='short_obj.png')


查看最短路径图short_obj.png:

三、 第三方库 -pympler

pympler工具特别适合给当前所有的 objects 的内存占用情况做简单统计 tracker对象初始化的时候会创建一个summary,每次调用tracker.print_diff。

安装

pip install pympler 

()的时候又会创建一个summary,当前的summary与上次的summary做比较,并打印两者之间的不同。如:

from pympler import tracker
tr = tracker.SummaryTracker()
# 建立快照一样对比不同
tr.print_diff()
x = []
y = [x, [x], dict(x=x)]
# 建立快照一样对比不同
tr.print_diff()
y23 = [x, [x], dict(x=x)]
del x
# 建立快照一样对比不同
tr.print_diff()


输出的结果:

                  types |   # objects |   total size
======================= | =========== | ============
                   list |        4509 |    229.99 KB
                    str |        4460 |    206.81 KB
                    int |         331 |      4.56 KB
                   dict |           2 |    200     B
  function (store_info) |           1 |     72     B
                   cell |           2 |     56     B
                weakref |           1 |     44     B
                 method |          -1 |    -36     B
                   code |          -2 |   -160     B
                  tuple |         -18 |   -992     B
  types |   # objects |   total size
======= | =========== | ============
   dict |           1 |    388     B
   list |           4 |    172     B
   type |           0 |    144     B
    str |           1 |     46     B
  types |   # objects |   total size
======= | =========== | ============
   dict |           1 |    148     B
   list |           2 |     

说明

部分的图或资料来互联网收集整理,如有侵权,烦请联系,我会立即进行删除。

End

纯属个人实践中相关经验之谈,如有纰漏,还希望大佬们多多提点!小钟同学 | 文  【原创】| QQ:308711822

如果文章对您有所帮助,不如四连击一下:点在+分享+收藏+关注呗 哈哈

如有错误之处也希望能帮忙指出。非常感谢!


文章转载自小儿来一壶枸杞酒泡茶,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论