Python 垃圾回收机制
Python 中一切皆对象。因此,你所看到的一切变量,本质上都是对象的一个指针。
那么,怎么知道一个对象,是否永远都不能被调用了呢?
就是当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。(没人调用它了)
import os
import psutil
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss/ 1024/ 1024
print("{} memory used: {} MB".format(hint, memory))
def func():
show_memory_info("initial")
a = [i for i in range(10000000)]
show_memory_info("after a created")
func()
show_memory_info("finished")
循环引用
如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?
def func():
show_memory_info("initial")
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info("after a, b created")
a.append(b)
b.append(a)
func()
show_memory_info("finished")
-----------------------------------------------
'''
引用计数
'''
import sys
import gc
# 清除没有引用的对象
# gc.collect()
a = []
# 2 查看变量的引用次数 本身也要算一次
# print(sys.getrefcount(a)) # 2
def func(a):
# 1 a = []
# 2 函数调用
# 3 函数的参数
# 4 getrefcount
print(sys.getrefcount(a)) # 4
func(a)
# b = a
# print(sys.getrefcount(a)) # 3次
# 引用次数为0 是垃圾回收启动的充分必要条件嘛?
# import objgraph
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
# objgraph.show_refs([a])
调试内存泄漏
虽然有了自动回收机制,但这也不是万能的,难免还是会有漏网之鱼。
内存泄漏是我们不想见到的,而且还会严重影响性能。有没有什么好的调试手段呢?
它就是 objgraph,一个非常好用的可视化引用关系的包。在这个包中,我主要推荐两个函数,
第一个是 show_refs(),它可以生成清晰的引用关系图。
import objgraph
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
objgraph.show_refs([a])
dot转图片:https://onlineconvertfree.com/zh/
总结
- 垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;
- 引用计数是其中最简单的实现,不过切记,这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收;
- Python 的自动回收算法包括标记清除和分代收集,主要针对的是循环引用的垃圾收集;
- 调试内存泄漏方面, objgraph 是很好的可视化分析工具。
代码调试
代码调试的两个方法:pdb,cProfile
# import pdb
#
# a = 1
# b = 2
#
# def func():
# print('enter func()')
#
# pdb.set_trace()
# c = 3
# func()
# print(a + b + c)
import cProfile
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def fib_seq(n):
res = []
if n > 0:
res.extend(fib_seq(n-1))
res.append(fib(n))
return res
# print(fib_seq(30))
cProfile.run('fib_seq(30)')
----------------------------------------
7049218 function calls (96 primitive calls) in 2.832 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
7049123/31 2.832 0.000 2.832 0.091 2.py:17(fib)
31/1 0.000 0.000 2.832 2.832 2.py:25(fib_seq)
1 0.000 0.000 2.832 2.832 <string>:1(<module>)
1 0.000 0.000 2.832 2.832 {built-in method builtins.exec}
31 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
30 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
方法名字的字符串调用方法
class Triangle:
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def get_area(self):
a, b, c = self.a, self.b, self.c
p = (a + b + c) / 2
return (p * (p - a) * (p - b) * (p - c)) ** 0.5
class Rectangle:
def __init__(self, a, b):
self.a, self.b = a, b
def getArea(self):
return self.a * self.b
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return self.r ** 2 * 3.14159
shape1 = Triangle(3, 4, 5)
shape2 = Rectangle(4, 6)
shape3 = Circle(1)
def get_area(shape):
method_name = ['get_area', 'getArea', 'area']
for name in method_name:
f = getattr(shape, name, None) #getattr函数通过字符串来调用方法
if f:
return f()
# map 函数
# print(get_area(shape1))
# print(get_area(shape2))
# print(get_area(shape3))
shape_list = [shape1, shape2, shape3]
#map() 会根据提供的函数对指定序列做映射。
#语法:map(function, iterable, ...)
area_list = list(map(get_area, shape_list))
print(area_list)
-----------------------------------------------
[6.0, 24, 3.14159]
经典的参数错误
### 不可变类型
以int类型为例:实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int对象,i引用自这个新的对象。
### 可变类型
以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改。
def add(a,b):
a += b
return a
a = 1
b = 2
c = add(a,b)
print(c)
print(a,b)
a = [1,2]
b = [3,4]
c = add(a,b)
print(c)
print(a,b)
a = (1,2)
b = (3,4)
c = add(a,b)
print(c)
print(a,b)
代码调试的两个方法:pdb,cProfile
# import pdb
#
# a = 1
# b = 2
#
# def func():
# print('enter func()')
#
# pdb.set_trace()
# c = 3
# func()
# print(a + b + c)
import cProfile
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def fib_seq(n):
res = []
if n > 0:
res.extend(fib_seq(n-1))
res.append(fib(n))
return res
# print(fib_seq(30))
cProfile.run('fib_seq(30)')
----------------------------------------
7049218 function calls (96 primitive calls) in 2.832 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
7049123/31 2.832 0.000 2.832 0.091 2.py:17(fib)
31/1 0.000 0.000 2.832 2.832 2.py:25(fib_seq)
1 0.000 0.000 2.832 2.832 <string>:1(<module>)
1 0.000 0.000 2.832 2.832 {built-in method builtins.exec}
31 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
30 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
class Triangle:
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def get_area(self):
a, b, c = self.a, self.b, self.c
p = (a + b + c) / 2
return (p * (p - a) * (p - b) * (p - c)) ** 0.5
class Rectangle:
def __init__(self, a, b):
self.a, self.b = a, b
def getArea(self):
return self.a * self.b
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return self.r ** 2 * 3.14159
shape1 = Triangle(3, 4, 5)
shape2 = Rectangle(4, 6)
shape3 = Circle(1)
def get_area(shape):
method_name = ['get_area', 'getArea', 'area']
for name in method_name:
f = getattr(shape, name, None) #getattr函数通过字符串来调用方法
if f:
return f()
# map 函数
# print(get_area(shape1))
# print(get_area(shape2))
# print(get_area(shape3))
shape_list = [shape1, shape2, shape3]
#map() 会根据提供的函数对指定序列做映射。
#语法:map(function, iterable, ...)
area_list = list(map(get_area, shape_list))
print(area_list)
-----------------------------------------------
[6.0, 24, 3.14159]
### 不可变类型
以int类型为例:实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int对象,i引用自这个新的对象。
### 可变类型
以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改。
def add(a,b):
a += b
return a
a = 1
b = 2
c = add(a,b)
print(c)
print(a,b)
a = [1,2]
b = [3,4]
c = add(a,b)
print(c)
print(a,b)
a = (1,2)
b = (3,4)
c = add(a,b)
print(c)
print(a,b)