查看原文
其他

数据治理 | 不丢数据的秘籍:了解Python的内存管理机制

分享小技巧的 数据Seminar 2022-12-31

目录

一、前言

二、 Python 的内存管理机制

  1. 浅复制

  2. 深复制

  3. 内存清理

三、结束语

本文共3180个字,阅读大约需要8分钟,欢迎指正!


Part1前言

在使用 Python 操作数据时,你是否遇到过这样的情况,当你不能确定一个步骤能否成功时,便使用代码另外复制了一份数据以防操作失误导致数据丢失,但当你修改了其中一个数据后,另一个复制的数据也同样发生改变,导致你的处理工作前功尽弃。其实这是不够了解 Python 导致的 “惨剧”,这篇文章我们就来讨论一下 Python 的内存管理机制,让你不再踩 “无效备份” 的坑。

Part2Python 的内存管理机制

变量通过变量指针引用对象,变量指针指向具体对象的内存空间,取对象的值。变量名没有类型,类型属于对象(变量引用对象,所以类型取决于对象),变量引用什么类型的对象,变量就是什么类型。Python 中的 id() 方法用于获取变量对象的内存地址。语法 A is B 用于判断两个引用所指的对象是否相同,若相同,返回 True,否则返回 False。

1浅复制

当浅复制一个变量时,内存不会为复制的变量分配空间,而是会将复制的变量指向被复制变量所指向的内存空间,说简单些,就是为数据起了个别名。当修改复制的变量时,实际上修改的是两者共同指向的内存中的对象,此时被复制的变量的值也随之改变。而删除复制的变量时,只是显式地销毁了变量名,即复制的引用方式,不会直接删除变量名所指向内存中的对象。例如:>>> A = [1,2,3]
>>> id(A)
1980982635784

>>> B = A    # 浅拷贝的命令
>>> id(B)
1980982635784

>>> B[0] = 9  # 修改其中一个数据
>>> B
[9, 2, 3]

>>> A        # 另一个数据同样发生改变
[9, 2, 3]

>>> del B    # 删除其中一个变量,另一个依然存在
>>> A
[9, 2, 3]

2深复制

前面提到浅复制,当然也有深复制(也称深拷贝)。浅复制的变量会与被复制的变量共用一个内存对象,改变其中一个,另一个也随之改变。但是当我们不希望两个变量同步改变时就可以使用深复制,深复制的对象会在内存中重新申请空间,存储与被复制变量相同的对象,即使被复制的对象发生改变或者被删除,也不会影响深复制的的变量。深复制的语法和应用实例如下。>>> import copy     # 导入 Python 标准库 copy
>>> var1 = [4,5,6]
>>> var2 = copy.deepcopy(var1) # 使用 copy 库深拷贝数据
>>> id(var1) == id(var2)
False

>>> var1[1] = 10    # 修改其中一个
>>> var1
[4, 10, 6]

>>> var2      # 另一个没有发生改变
[4, 5, 6]

>>> del var1
>>> var2  # 深拷贝的变量不受被拷贝变量的任何影响
[4, 5, 6]
有时候你没有进行任何拷贝操作,不同的变量也可能会指向同一个对象,究竟是怎么回事呢?Python 可以使用语法 A is B 进行引用所指判断,判断两个变量 A 和 B 的所指向的内存空间是否相同。下面我们来探索一下:>>> # 整数型
>>> var3 = 100
>>> var4 = 100
>>> var3 is var4  # 两个变量竟然使用同一个内存对象
True

>>> # 浮点数型
>>> var5 = 2.3
>>> var6 = 2.3
>>> var5 is var6
False

>>> # 短字符串
>>> var7 = 'good'
>>> var8 = 'good'
>>> var7 is var8
True

>>> # 长字符串
>>> var9 = 'very good!'
>>> var10 = 'very good!'
>>> var9 is var10
False

>>> # 列表
>>> var11 = [1,2]
>>> var12 = [1,2]
>>> var11 is var12
False
由运行结果可知,Python缓存了整数和短字符串,因此每个对象在内存中只存有一份,引用所指对象就是相同的,即使分别使用赋值语句,也只是创造新的引用,而不是对象本身。Python没有缓存长字符串(可以简单理解为纯字母、数字或者下划线 “_” 的组合 就是短字符串,其他的是长字符串。)、列表及其他对象。Python可以使用 标准库 sysgetrefcount() 方法获取一个变量所指向对象被引用的次数。值得注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount() 所得到的结果,总会比期望的多1。例如:

3内存清理

为了节省内存空间,当 Python 中的对象越来越多,占据越来越大的内存,启动垃圾回收(garbage collection),将没用的对象清除。其原理是,当 Python 的某个对象的被引用次数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾。比如某个新建对象,被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被当作垃圾回收。>>> var15 = 3245
>>> sys.getrefcount(var15)
2
>>> del var15
>>> sys.getrefcount(var15)    # 系统报错,提示变量未定义
Traceback (most recent call last):
  File "<pyshell#130>", line 1, in <module>
    sys.getrefcount(A)
NameError: name 'var15' is not defined
使用 del var15 之后,已经没有任何引用指向之前建立的 3245,该对象引用计数变为0,此时用户不可能通过任何方式接触或者动用这个对象,当垃圾回收启动时,Python扫描到这个引用计数为0的对象,就将它所占据的内存清空。但是进行垃圾回收时,Python不能进行其他的任务,因此频繁地进行垃圾回收会大大降低 Python 工作效率。Python只会在特定情况下才会主动进行垃圾回收,当垃圾对象少时就没有必要进行垃圾回收。当然,Python也支持主动进行垃圾回收,语法如下:>>> import gc
>>> gc.collect()
为了你的数据安全,通常不建议主动使用 gc.collect() 来清理内存,因为这种命令是危险的,如果使用不当,可能会导致内存中的数据丢失。

Part3结束语

浅拷贝与深拷贝的概念与操作是每一个“数据人”必须掌握的技能,也几乎是每一位同学必踩的坑,小编希望你在踩坑之前就看到这篇文章,掌握 Python 的内存机制,避免数据丢失。
我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系。该板块将涉及以下几个模块(点击标题即可跳转至相应合集):
  1. 计算机基础知识
  2. 编程基础
  3. 数据采集
  4. 数据存储
  5. 数据清洗
  6. 数据实验室搭建
  7. 数据治理特别篇



星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!

点击搜索你感兴趣的内容吧

往期推荐


基本无害 | 第三章第二节(全)——回归和因果关系

数据可视化 | 那些年我们一起追过的温网

基本无害 | 使回归有意义——异质性和非线性(1)

数据治理 | 有效防止跑数据卡顿!社科人必须掌握的计算机知识

数据治理 | 省下一个亿!一文读懂如何用python读取并处理PDF中的表格(赠送本文所用的PDF文件)





数据Seminar




这里是大数据、分析技术与学术研究的三叉路口


文 | 《社科领域大数据治理实务手册》


    欢迎扫描👇二维码添加关注    

点击下方“阅读全文”了解更多

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存