垃圾回收机制
- 在python中创建变量的时候,系统会自动在内存中开辟一个空间,用于存储数据,但是内存是有限的,而创建的变量不一定全部都被使用,但是又占用这内存,如果不加以管理的话,会导致内存溢出,程序直接中止。所以python有自己的垃圾回收机制。
引用计数
- 在python中,一个变量被引用一次就记一次数,可以使用sys.getrefcount()方法查看对象引用次数。
- 如下所示
- 一般当变量引用次数为零时,系统会回收该变量。
- 当getrefcount(a)也会算一次引用,但是一个程序多次使用该方法,只会算一次引用。
- 当调用func函数的时候,a被引用了两次,调用一次,传参一次。而当函数运行结束时,a只被引用了两次,说明在函数内的引用被系统自动释放了。 ```c from sys import getrefcount
1
a = [1,2,3]
2
print(getrefcount(a))
4
def func(a): print(getrefcount(a))
3
func(a)
print(getrefcount(a))
![image.png](https://cdn.nlark.com/yuque/0/2020/png/1325594/1594518544940-ab0c5b08-bcec-422f-bbd3-1e72ab341cba.png#align=left&display=inline&height=61&margin=%5Bobject%20Object%5D&name=image.png&originHeight=103&originWidth=1269&size=30016&status=done&style=none&width=746)
<a name="NkaRs"></a>
#### 循环引用
- 有些特殊情况,当变量的引用次数即使为零,也不一定会被系统主动回收。比如两个变量循环引用。
- 可以使用gc.collect()尽行回收,当不主动回收时,内存变化如下
```c
def show_memery(s):
# 获取当前进程
pid = os.getpid()
# 获取当前对象
p = psutil.Process(pid)
# 获取内存信息
info = p.memory_full_info()
# 内存占用
m = info.uss/1024/1024
print(f"{s}: 占用{m}MB")
def func():
a = [i for i in range(1000000)]
b = [i for i in range(1000000)]
a.append(b)
b.append(a)
show_memery("created")
show_memery("start")
func()
# gc.collect()
show_memery("end")
- gc.collect()主动回收后的结果
weakref
- 弱引用:如果一个类实例化对象除了弱引用,其他实例被销毁,则弱引用对象也会被销毁,可以避免循环引用。 ```python
import weakref import sys
class MyClass(object): def del(self): print(self, “del“)
my_class = MyClass()
弱引用my_class
your_class = weakref.ref(my_class)
查看my_class、your_class地址
print(hex(id(my_class)), hex(id(your_class)))
删除实例对象
del my_class
print(your_class)
<a name="MkXY8"></a>
#### ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1325594/1594533286845-8f316891-2497-4ad3-a75b-3c1a8e085252.png#align=left&display=inline&height=48&margin=%5Bobject%20Object%5D&name=image.png&originHeight=81&originWidth=1252&size=48469&status=done&style=none&width=746)
<a name="6T6F5"></a>
####
<a name="2hRk2"></a>
#### 单例模式
- 当一个类被创建,需要被多个模块调用多次时,可以使用单例模式以节约内存,如下所示,创建的sing1和sing2都是同一个地址,类似于a = b = 1, a和b都是指向同一个地址。
- 和弱引用不同的是,单例模式中,删除实例对象对其他的实例并没有影响。
```python
class MySing(object):
__instance = None
def __new__(cls):
# 如果__instance 为None的话创建一个子类 并反回
# 当__instance已经被创建了,就直接返回__instance
if cls.__instance is None:
__instance = super().__new__(cls)
return __instance
else:
return __instance
sing1 = MySing()
sing2 = MySing()
print(id(sing1), id(sing2))
slots
- 动态绑定
- 如下所示,类的属性使用dict方式打印,即通过字典形式绑定,所以我们可以动态的进行修改。
class People(object):
def __init__(self,name,age):
self.name = name
self.age = age
people = People("lulu", 18)
people.age = 21
print(people.__dict__)
- 使用字典形式查询速度比较快,在创建少量的实例时,不会对内存有太大影响,但是如果实例非常多的话,十分影响占用内存。如下,占用1726kb内存。 ```python import tracemalloc
class People(object):
# __slots__ = "name", "age"
def __init__(self, name, age):
self.name = name
self.age = age
开始跟踪内存分配
tracemalloc.start()
people = [People(“666”, 1) for i in range(10000)]
当前内存分配快照
snapshot = tracemalloc.take_snapshot()
当前对象统计的监测文件
top = snapshot.statistics(“filename”)
for start in top[:10]: print(start)
![image.png](https://cdn.nlark.com/yuque/0/2020/png/1325594/1594535201330-d1455e65-438d-48e0-aa93-0ef024a8b6a2.png#align=left&display=inline&height=26&margin=%5Bobject%20Object%5D&name=image.png&originHeight=46&originWidth=1308&size=37006&status=done&style=none&width=746)
- 可以通过使用__slots__关闭动态绑定,如下
- 可以使用dir方法查看类的属性,但是已经没有__dict__方法了
- __slots__方法不会被子类继承
```python
class People(object):
__slots__ = "name", "age"
def __init__(self,name,age):
self.name = name
self.age = age
people = People("lulu", 18)
print(dir(people))
print(people.__dict__)
- 关闭动态绑定之后占用内存 632kb
深拷贝浅拷贝
- 浅拷贝:引用变量的地址,并不会新创建一个内存空间
- 因为python属于动态语言,所以会尽量节省空间以提升效率,默认使用浅拷贝。如下所示,li和li2指向的是同一个地址。
- 当为li添加加一个元素的时候,li2也也随之添加了一个元素 ```python li = [1,2,3]
li2 = li
print(id(li), id(li2))
li.append(1) print(li2)
![image.png](https://cdn.nlark.com/yuque/0/2020/png/1325594/1594608901825-b24c1293-ea09-4012-8d32-22dc0daa5947.png#align=left&display=inline&height=40&margin=%5Bobject%20Object%5D&name=image.png&originHeight=67&originWidth=1247&size=24976&status=done&style=none&width=746)
- 那么如果想要将li的值复制一份,放入新的地址上,那么就需要使用deepcopy方法,如下
- 可见,li和li2的地址不同,并且修改了li,li2的值也不会改变
```python
from copy import deepcopy
li = [1,2,3]
li2 = deepcopy(li)
print(id(li), id(li2))
li.append(1)
print(li2)