Python3 内存机制

本文中交替出现 Python 的编译模式和交互模式代码块,为便于区分,带有 >>> 的 Python 代码块为交互模式,其余 Python 代码块为编译模式。

可变对象与不可变对象

可变对象 不可变对象
列表、字典、集合 整型、浮点型、布尔型、字符串、元组

简单来说,可变对象就是指在修改数据时,直接修改原来的数据对象;不可变对象则是创建一个新的对象,并且将变量的引用(相当于C++中的指针)转移到新创建的对象上。

不可变对象实例

  • int
1
2
3
4
5
6
>>>a = 6
>>>id(a)
140725289698248
>>>a = 7
>>>id(a)
140725289698280
  • string
1
2
3
4
5
6
7
8
9
10
11
>>>string = "ykx!!!"
>>>new_string = string
>>>id(string)
2053870338288
>>>id(new_string)
2053870338288
>>>new_string = string.replace("!","?")
>>>new_string
'ykx???'
>>>id(new_string)
2053827268784

可变对象实例

  • dict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>>tinydic = {"ykx":"大帅哥","hhy":"憨憨"}
>>>new_tinydic = {"Mercedes":"cool"}
>>>id(tinydic)
2053870017280
>>>id(new_tinydic)
2053870017728
>>>tinydic.update(new_tinydic)
>>>tinydic
{'ykx': '大帅哥', 'hhy': '憨憨', 'Mercedes': 'cool'}
>>>id(tinydic)
2053870017280
>>>id(new_tinydic)
2053870017728
>>>new_tinydic["Mercedes"] = "considerable"
id(new_tinydic)
2053870017728

深拷贝与浅拷贝

在Python中,拷贝对象时有两种方式:浅拷贝(shallow copy)和深拷贝(deep copy)。理解它们之间的区别对处理复杂的数据结构非常重要。

浅拷贝(Shallow Copy)

拷贝对象的引用。当原对象的数据改变时,拷贝的对象也会发生改变。

浅拷贝创建一个新的对象,但不会递归地复制对象中包含的所有子对象。对于包含的子对象,浅拷贝只会复制它们的引用。

可以使用 copy 模块中的 copy() 函数或对象的 copy() 方法来进行浅拷贝。

深拷贝(Deep Copy)

创建一个新的对象并将原数据存入新的对象。原对象数据改变不影响拷贝对象

深拷贝创建一个新的对象,并递归地复制所有包含的对象,形成一个独立的副本。深拷贝后的对象与原对象完全独立,修改其中一个不会影响另一个。

可以使用 copy 模块中的 deepcopy() 函数来进行深拷贝。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy

original_list = [1, 2, [3, 4]]
shallow_copied_list = copy.copy(original_list)

print(original_list) # 输出: [1, 2, [3, 4]]
print(shallow_copied_list) # 输出: [1, 2, [3, 4]]

# 修改原列表中的子列表
original_list[2][0] = 99

print(original_list) # 输出: [1, 2, [99, 4]]
print(shallow_copied_list) # 输出: [1, 2, [99, 4]]

在这个例子中,修改 original_list 中的子列表也影响了 shallow_copied_list,因为它们共享同一个子列表的引用。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy

original_list = [1, 2, [3, 4]]
deep_copied_list = copy.deepcopy(original_list)

print(original_list) # 输出: [1, 2, [3, 4]]
print(deep_copied_list) # 输出: [1, 2, [3, 4]]

# 修改原列表中的子列表
original_list[2][0] = 99

print(original_list) # 输出: [1, 2, [99, 4]]
print(deep_copied_list) # 输出: [1, 2, [3, 4]]

在这个例子中,修改 original_list 中的子列表不会影响 deep_copied_list,因为深拷贝创建了一个完全独立的副本。

值得注意的是,python3中列表默认的的.copy()方法是浅拷贝,但是remove()方法会改变深浅。

示例

1
2
3
4
5
6
7
>>> original_list = [1, 2, [3, 4]]
>>> shallow_copied_list = original_list.copy()
>>> original_list[2][0] = 99
>>> original_list
[1, 2, [99, 4]]
>>> shallow_copied_list
[1, 2, [99, 4]]

在这个例子中,修改 original_list 中的子列表也影响了 shallow_copied_list,因为它们共享同一个子列表的引用。

1
2
3
4
5
6
7
8
>>> a = ["safdf",1,2,3,5,4,8,7]
>>> b = a.copy()
>>> a.remove(1)
>>> a
['safdf', 2, 3, 5, 4, 8, 7]
>>> b
['safdf', 1, 2, 3, 5, 4, 8, 7]
>>> b = a.copy()

在这个例子中,使用remove()方法后会再次开辟一块内存深拷贝a,a和b就不属于同一引用了。

使用 copy 模块进行浅拷贝和深拷贝

1
2
3
4
5
6
7
8
import copy

# 浅拷贝
shallow_copy = copy.copy(original_object)
shallow_copied_list = original_list.copy()

# 深拷贝
deep_copy = copy.deepcopy(original_object)

适用场景

  • 浅拷贝适用于对象层次结构较浅且只需要复制最外层的情况。
  • 深拷贝适用于对象层次结构较深且需要完整独立副本的情况。

注意事项

  1. 浅拷贝在处理不可变对象(如整数、字符串、元组)时,行为与深拷贝相同,因为这些对象本质上不会被修改。
  2. 深拷贝可能会比较耗时和耗内存,特别是对于大型复杂对象。

理解浅拷贝和深拷贝的区别,以及如何在Python中正确使用它们,对于编写健壮的代码非常重要。

字典中的深浅拷贝问题

浅拷贝:只拷贝对象的引用

**深拷贝:**再开辟一块内存,新建立一条引用储存拷贝对象

相关库

copy

  • copy.copy() 或 直接赋值:浅拷贝

  • copy.deepcopy()深拷贝完全拷贝了父对象及其子对象。

深浅拷贝举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import copy
dic_mother = {"母体":"111"}
dic_son = {"1":"111"}

dic_mother["子体"] = dic_son

print(f"刚刚初始化的母体字典{dic_son}")
print(f"刚刚初始化的母体字典{dic_mother}")

dic_mother_shallowcopy = dic_mother #直接赋值,属于浅拷贝
dic_mother_shallowcopy["子体"]["1"] = "222"
print(f"浅拷贝并修改后的的母体字典{dic_mother_shallowcopy}")
print(f"子体字典{dic_son}")

dic_mother_deepcopy = copy.deepcopy(dic_mother)
dic_mother_deepcopy["子体"]["1"] = "333"
print(f"浅拷贝并修改后的的母体字典{dic_mother_deepcopy}")
print(f"子体字典{dic_son}")

输出结果

1
2
3
4
5
6
刚刚初始化的母体字典{'1': '111'}
刚刚初始化的母体字典{'母体': '111', '子体': {'1': '111'}}
浅拷贝并修改后的的母体字典{'母体': '111', '子体': {'1': '222'}}
子体字典{'1': '222'}
浅拷贝并修改后的的母体字典{'母体': '111', '子体': {'1': '333'}}
子体字典{'1': '222'}

可以看出深浅拷贝copy.copy():深拷贝父对象(一级目录),子对象(二级目录)不拷贝,子对象是引用