垃圾回收机制

  • 在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))

  1. ![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)
  2. <a name="NkaRs"></a>
  3. #### 循环引用
  4. - 有些特殊情况,当变量的引用次数即使为零,也不一定会被系统主动回收。比如两个变量循环引用。
  5. - 可以使用gc.collect()尽行回收,当不主动回收时,内存变化如下
  6. ```c
  7. def show_memery(s):
  8. # 获取当前进程
  9. pid = os.getpid()
  10. # 获取当前对象
  11. p = psutil.Process(pid)
  12. # 获取内存信息
  13. info = p.memory_full_info()
  14. # 内存占用
  15. m = info.uss/1024/1024
  16. print(f"{s}: 占用{m}MB")
  17. def func():
  18. a = [i for i in range(1000000)]
  19. b = [i for i in range(1000000)]
  20. a.append(b)
  21. b.append(a)
  22. show_memery("created")
  23. show_memery("start")
  24. func()
  25. # gc.collect()
  26. show_memery("end")

image.png

  • gc.collect()主动回收后的结果

image.png

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)

  1. <a name="MkXY8"></a>
  2. #### ![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)
  3. <a name="6T6F5"></a>
  4. ####
  5. <a name="2hRk2"></a>
  6. #### 单例模式
  7. - 当一个类被创建,需要被多个模块调用多次时,可以使用单例模式以节约内存,如下所示,创建的sing1和sing2都是同一个地址,类似于a = b = 1, a和b都是指向同一个地址。
  8. - 和弱引用不同的是,单例模式中,删除实例对象对其他的实例并没有影响。
  9. ```python
  10. class MySing(object):
  11. __instance = None
  12. def __new__(cls):
  13. # 如果__instance 为None的话创建一个子类 并反回
  14. # 当__instance已经被创建了,就直接返回__instance
  15. if cls.__instance is None:
  16. __instance = super().__new__(cls)
  17. return __instance
  18. else:
  19. return __instance
  20. sing1 = MySing()
  21. sing2 = MySing()
  22. print(id(sing1), id(sing2))

image.png

slots

  • 动态绑定
    • 如下所示,类的属性使用dict方式打印,即通过字典形式绑定,所以我们可以动态的进行修改。
  1. class People(object):
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. people = People("lulu", 18)
  6. people.age = 21
  7. print(people.__dict__)

image.png

  • 使用字典形式查询速度比较快,在创建少量的实例时,不会对内存有太大影响,但是如果实例非常多的话,十分影响占用内存。如下,占用1726kb内存。 ```python import tracemalloc

class People(object):

  1. # __slots__ = "name", "age"
  2. def __init__(self, name, age):
  3. self.name = name
  4. 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)

  1. ![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)
  2. - 可以通过使用__slots__关闭动态绑定,如下
  3. - 可以使用dir方法查看类的属性,但是已经没有__dict__方法了
  4. - __slots__方法不会被子类继承
  5. ```python
  6. class People(object):
  7. __slots__ = "name", "age"
  8. def __init__(self,name,age):
  9. self.name = name
  10. self.age = age
  11. people = People("lulu", 18)
  12. print(dir(people))
  13. print(people.__dict__)

image.png

  • 关闭动态绑定之后占用内存 632kb

image.png

深拷贝浅拷贝

  • 浅拷贝:引用变量的地址,并不会新创建一个内存空间
  • 因为python属于动态语言,所以会尽量节省空间以提升效率,默认使用浅拷贝。如下所示,li和li2指向的是同一个地址。
  • 当为li添加加一个元素的时候,li2也也随之添加了一个元素 ```python li = [1,2,3]

li2 = li

print(id(li), id(li2))

li.append(1) print(li2)

  1. ![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)
  2. - 那么如果想要将li的值复制一份,放入新的地址上,那么就需要使用deepcopy方法,如下
  3. - 可见,lili2的地址不同,并且修改了lili2的值也不会改变
  4. ```python
  5. from copy import deepcopy
  6. li = [1,2,3]
  7. li2 = deepcopy(li)
  8. print(id(li), id(li2))
  9. li.append(1)
  10. print(li2)

image.png