更多、更及时内容欢迎留意 微信公众号 小窗幽记机器学习

在用Python搭建服务过程使用字典存放自定义的对象,需要特别指出的是value值是占用内存空间较大的对象。随着时间的流逝和数据的累积,字典的key变得越来越多,从而使得整个字典对象占用过大的内存空间。此时,需要根据实际需要定期删除特定的keys,及时释放内存,否则就可能引发血案:OOM,进程被kill。

字典内存释放

众所周知,去掉字典中元素可以使用 pop 或者 del 方法,但是这两种方法都没有真正地释放对应的元素的空间。Python 为了保证hash table 探测链的完整,对于那个被删除的key只是被标记成了空,并没有真正被删除掉,所以该字典的内存占用没有得到释放。这是为了避免多度重建hash table。那么,如何真正释放这部分内存空间呢?可以创建或者拷贝一个旧字典再覆盖掉新字典。具体示例如下:

import sys
import gc
import copy
a = {}
print("init empty dict memory size={} bytes".format(sys.getsizeof(a)))
for i in range(10**6):
    a[i] = i
print("after set value, dict memory size={} bytes".format(sys.getsizeof(a)))
for i in range(10**6):
    del a[i]
    # a.pop(i)
print("after del, dict memory size={} bytes".format(sys.getsizeof(a)))
a_new = dict(a)
print("after init a new one, dict memory size={} bytes".format(sys.getsizeof(a_new)))
b = copy.copy(a)
print("after copy a new one, dict memory size={} bytes".format(sys.getsizeof(b)))
c = copy.deepcopy(a)
print("after deepcopy a new one, dict memory size={} bytes".format(sys.getsizeof(c)))

运行结果如下:

init empty dict memory size=240 bytes
after set value, dict memory size=41943144 bytes
after del, dict memory size=41943144 bytes
after init a new one, dict memory size=240 bytes
after copy a new one, dict memory size=240 bytes
after deepcopy a new one, dict memory size=240 bytes

笔者在实验过程中还发现一个有趣的问题:字典调用 clear 操作后的内存占用比新建一个字典的内存占用小。具体示例如下:

dict = {}
print(sys.getsizeof(dict))  # 240, 这因为新的字典的 size 是 PyDict_MINSIZE
dict.clear()
print(sys.getsizeof(dict))  # 72

这是因为新建字典是按照PyDict_MINSIZE 分配keyspace。当调用.clear()函数后,keyspace 被重新分配到一个静态的空keyspace: Py_EMPTY_KEYS,此时的字典是真的empty。

上述示例代码中使用浅拷贝和深拷贝的方式释放空间,那么这里进一步介绍下直接赋值、浅拷贝和深拷贝之间的区别。

浅拷贝 Vs 深拷贝

简而言之:

  • 直接赋值:其实就是对象的引用(别名)。

  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。

  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

浅拷贝的示例代码如下:

class test_obj:
    def __init__(self, obj_id, text, label='0'):
        self.obj_id = obj_id
        self.text = text
        self.label = label
ob1 = test_obj("id1", "文本1", 'label1')
a = {"k1": "12345", "k2": [0, 1, 2], "k3": ob1}
b = a.copy()  # 浅拷贝
print("b=", b)
a["k2"].append(4)  # 由于是浅拷贝,所以key所引用的对象是相同的,所以会造成b的值也随a变化
print("Append a")
print("a=", a)
print("b=", b)
# 如果是 pop 掉key的话
print("Pop b:")
b.pop("k2")
# a.pop("k2")
print("a=", a)
print("b=", b)
# 修改值
print("Modify object value:")
print("values of a=", vars(a["k3"]))
b["k3"].text = "换文本了"
print("values of a=", vars(a["k3"]))

运行结果如下:

b= {'k1': '12345', 'k2': [0, 1, 2], 'k3': <__main__.test_obj object at 0x7f1aa09e8f60>}
Append a
a= {'k1': '12345', 'k2': [0, 1, 2, 4], 'k3': <__main__.test_obj object at 0x7f1aa09e8f60>}
b= {'k1': '12345', 'k2': [0, 1, 2, 4], 'k3': <__main__.test_obj object at 0x7f1aa09e8f60>}
Pop b:
a= {'k1': '12345', 'k2': [0, 1, 2, 4], 'k3': <__main__.test_obj object at 0x7f1aa09e8f60>}
b= {'k1': '12345', 'k3': <__main__.test_obj object at 0x7f1aa09e8f60>}
Modify object value:
values of a= {'obj_id': 'id1', 'text': '文本1', 'label': 'label1'}
values of a= {'obj_id': 'id1', 'text': '换文本了', 'label': 'label1'}

浅拷贝下,a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(即为引用)。所以,子对象的值改变的话,两者都会改变;而 pop 或者 del 特定key,则由于是独立对象不会相互影响。所以当需要在程序中动态地删除某些key(特别是key所对应的value占用较大内存空间),且要释放内存空间的话可以使用浅拷贝的方式重新赋值,否则单纯 pop 特定key并不会释放字典的内存空间。

深拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。
深拷贝的示例代码:

import copy
class test_obj:
    def __init__(self, obj_id, text, label='0'):
        self.obj_id = obj_id
        self.text = text
        self.label = label
ob1 = test_obj("id1", "文本1", 'label1')
a = {"k1": "12345", "k2": [0, 1, 2], "k3": ob1}
b = copy.deepcopy(a)
print("a=", a)
print("b=", b)
a["k2"].append(4)
print("Append a")
print("a=", a)
print("b=", b)
# 如果是 pop 掉key的话
print("Pop b:")
b.pop("k2")
# a.pop("k2")
print("a=", a)
print("b=", b)
# 修改值
print("Modify object value:")
b["k3"].text = "换文本了"
print("values of a=", vars(a["k3"]))
print("values of b=", vars(b["k3"]))

运行结果如下:

a= {'k1': '12345', 'k2': [0, 1, 2], 'k3': <__main__.test_obj object at 0x7fbf660cdf98>}
b= {'k1': '12345', 'k2': [0, 1, 2], 'k3': <__main__.test_obj object at 0x7fbf660d30b8>}
Append a
a= {'k1': '12345', 'k2': [0, 1, 2, 4], 'k3': <__main__.test_obj object at 0x7fbf660cdf98>}
b= {'k1': '12345', 'k2': [0, 1, 2], 'k3': <__main__.test_obj object at 0x7fbf660d30b8>}
Pop b:
a= {'k1': '12345', 'k2': [0, 1, 2, 4], 'k3': <__main__.test_obj object at 0x7fbf660cdf98>}
b= {'k1': '12345', 'k3': <__main__.test_obj object at 0x7fbf660d30b8>}
Modify object value:
values of a= {'obj_id': 'id1', 'text': '文本1', 'label': 'label1'}
values of b= {'obj_id': 'id1', 'text': '换文本了', 'label': 'label1'}

【更多、更及时内容欢迎留意微信公众号小窗幽记机器学习

背景在用Python搭建服务过程使用字典存放自定义的对象,需要特别指出的是value值是占用内存空间较大的对象。随着时间的流逝和数据的累积,字典的key变得越来越多,从而使得整个字典对象占用过大的内存空间。此时,需要根据实际需要定期删除特定的keys,及时释放内存,否则就可能引发血案:OOM,进程被kill。字典内存释放众所周知,去掉字典中元素可以使用 pop 或者 del 方法,但是这两种方法都没有真正地释放对应的元素的空间。Python 为了保证hash table 探测链的完整,对于那个被删除的
deepcopy是对对象的引用的引用对象都进行递归copy,为了防止递归溢出,deepcopy会记录每次已经copy的对象,所以增加了存储操作复杂度,从而影响执行效率。 提速方法: 1.采用pickle模块进行序列化和反序列化 import copy import pickle import timeit class A: def __init__(self): self.array = [1,2,3] def cp(): a = A() for i in rang
我们想要创建一个dict_02,使得其值和dict_01相等,并且在改变dict_02的时候不影响dict_01 若直接相等,相当于dict_02变成了dict_01的引用,改变dict_02的时候dict_01的值也会随之变化 dict_01 = {"name": "张三", "age": 20} dict_02 = dict_01 print("dict_02:", dict_0 —–我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。  —–而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。 对于... 一、Python对象 我们经常听到:在Python中一切皆对象。其实,说的就是我们在Python中构造的任何数据类型都是一个对象,不管是数字、字符串、字典等常见的数据结构,还是函数,甚至是我们导入的模块等,Python都会把它当做是一个对象来处理。 所有的Python对象
SmilingCreeper: Traceback (most recent call last): File "D:/talkbot/mods/chatterbot.py", line 1, in <module> import chatterbot File "D:\talkbot/mods\chatterbot.py", line 2, in <module> from chatterbot.training.trainers import ChatterBotCorpusTrainer ModuleNotFoundError: No module named 'chatterbot.training'; 'chatterbot' is not a package 下载huggingface上模型的正确姿势 weixin_42393474: 一样的操作,不会下载lfs文件 遗传算法与C++实现 lanzai710: 请问一下,我在codeblock里运行,第25行main函数哪里报错是怎么回事