一、赋值、浅拷贝和深拷贝的区别
- 赋值
在python中,对象的赋值就是简单的对象引用。
1. a = [1,2,"hello",['python', 'C++']]
2. b = a
在上述情况下,a和b是一样的,它们指向同一片内存,b不过是a的别名,是引用。
赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了b这个名字之外,没有其他的内存开销。修改了a,也就影响了b,同理,修改了b,也就影响力a。
- 浅拷贝
- 浅拷贝是指创建一个新的对象,其内容是原对象中元素的引用(新对象与原对象共享内存中的子对象)。其内容非原对象的引用,而是原对象内第一层对象(对象子元素)的引用。
- 浅拷贝有三种形式:切片操作、工厂函数、copy模块中的copy函数
1. 比如上述的列表 a,
2. 切片操作:b = a[:] 或者 b = [x for x in a];
3. 工厂函数:b = list(a);
4. copy 函数:b = copy.copy(a);
- 浅拷贝之所以是浅拷贝,是因为它仅仅只拷贝了一层,在列表a中有一个嵌套的list,如果修改它,情况就不一样了。
比如:a[3].append(‘java’),查看列表b,会发现列表b也发生了变化,这是因为,我们修改了嵌套的list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是同一个位置。
- 深拷贝
- 深拷贝只有一种形式,copy模块中的deepcopy()函数。
- 深拷贝是指创建一个新的对象,然后递归的拷贝原对象所包含的子对象(排除最后一层)。深拷贝出来的对象与原对象没有任何关联。
- 深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。
- 同样的列表a,如果使用b=copy.deepcopy(a),在修改列表b将不会影响到列表a,即使嵌套的列表具有更深的层次,也不会产生影响,影响深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。
值得注意的是:
- 对于数字和字符串等不可变类型(原子类型)来说,赋值、浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址,都是对同一对象的引用。
- 如果元组变量包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。
二、Python的内存管理机制及调优手段
Python的内存管理是自动的,主要是由垃圾收集器和内存分配器组成。
Python(尤其是CPython实现)主要采用以下几种内存管理机制,其中包括引用计数、垃圾回收和内存池等技术。
1、引用计数
引用计数是一种非常高效的内存管理手段,当一个python对象被引用时其引用数增加1,当其不在被一个变量引用时则计数减1。当引用计数等于0时回收对象。
2、垃圾回收
1)引用计数
标记清引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。
- 2)标记清除
-
如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。
- 3)分代回收
因为垃圾回收机制每次回收内存,都需要将所有的对象引用计数都遍历一遍,这是非常耗时的,所以在历经多次扫描的情况下,都没有被回收的变量,垃圾回收机制就会将他们按等级划分,垃圾回收机制就会认为该变量是常用的变量,对其的扫描频率就会降低。这使得垃圾收集机制需要处理的内存少了,效率自然就提高了。
3、内存池
- Python的内存池是一个缓存区
- 用于管理小于256个字节的对象的内存分配
- 内存池机制主要用于管理小块内存对象,比如整型数值、字符串、元组等
- 当程序需要创建这些小块对象时,python会从内存池中分配一段内存空间,并将其划分为多个大小的块,保存在内存池中
- 当程序需要销毁这些对象时,python会将它们标记为未使用状态,并不会立即释放内存,而是保留在内存池以备再次使用,避免了频繁的内存分配和释放操作。
三、垃圾回收(Garbage Collection, GC)机制的原理
在Python中,使用引用计数进行垃圾回收,同时通过标记-清除算法解决容器对象可能产生的循环引用问题;最后通过分代回收算法提高垃圾回收效率。