Python垃圾回收及性能分析(通过实例方法名字的字符串调用方法、垃圾回收机制、手动启动垃圾回收、调试内存泄漏、pdb 进行代码调试)

通过实例方法名字的字符串调用方法

有三个图形类 Circle,Triangle,Rectangle
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口

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
  • getattr(x,“y”,None) --> 等同于 x.y 当x中不含有y时,返回None。
  • map(func,iterable) --> 将iterable中的元素一一映射到func函数中处理,并且返回新的map对象。
垃圾回收机制
介绍

在Python程序运行的时候,会在内存中开辟一块空间,用于存放临时变量;当计算完成之后,就会将结果输出到永久性存储器中。如果数据量特别大,那内存空间管理不妥当的话就非常容易爆内存,程序可能直接终止。

在Python中,一切皆对象。所以,每一个变量,实际上都是对象的一个指针。所以,当这个对象的引用计数(指针数)为0的时候,说明它也变成了垃圾,需要被放到回收箱中。

OS模块

与操作系统交互的库

psutil模块

与系统交互的库,能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息。它主要用来做系统监控,性能分析,进程管理。
通过以下代码检测程序在运行时的内存消耗

import os
import psutil

def show_info(start):
    pid = os.getpid()

    p = psutil.Process(pid)

    info = p.memory_full_info()

    memory = info.uss/1024./1024
    print(f"{start}一共占用{memory:.2f}MB")


def func():
    show_info("initial")
    a = [i for i in range(1000000)]   
    show_info("created")


func()
show_info("finished")
  • 当a是局部变量时,在返回到函数调用处时,局部变量的引用会注销。这时,列表a所指代对象的引用数为0,Python便会执行垃圾回收,因此之前占用的内存被收回了。
  • 当a是全局变量的时,即使函数体内代码执行完毕,返回到函数调用处时,对列表a的引用仍然是存在的,所以对象不会被垃圾回收,依然占有大量内存。
Python内部的引用计数机制

我们可以通过sys.getrefcount()这个函数,来了解Python内部的引用计数机制

import sys

a = [1,2,3]

print(sys.getrefcount(a))
  • getrefcount本身也会引入一次计数。
手动启动垃圾回收

如果我们可以手动删除完对象的引用,然后强制调用gc.collect()清除没有引用的对象,其实也就是手动的启动对象的回收。

循环引用

如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?

import gc
def show_info(start):
    pid = os.getpid()
    p = psutil.Process(pid)
    info = p.memory_full_info()
    memory = info.uss/1024./1024
    print(f"{start}一共占用{memory:.2f}MB")


def func():
    show_info("initial")
    a = [i for i in range(100000)]
    b = [i for i in range(100000)]
    show_info("after a,b created")

    # 相互引用
    a.append(b)
    b.append(a)

func()
gc.collect()
show_info("finished")

总而言之,当双向引用的时候,引用计数虽然还在,但我们可以手动拉起回收,进行释放内存。所以,引用次数是垃圾回收的 充分非必要条件

调试内存泄漏

在Python中通过引用计数和垃圾回收来管理内存,但是在一定情况下也会产生内存泄露

  • 第一是对象被另一个生命周期特别长的对象所引用
  • 第二是循环引用中的对象定义了__del__函数
    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/
在这里插入图片描述

用 pdb 进行代码调试

在这里插入图片描述
如何使用 pdb
首先,要启动 pdb 调试,我们只需要在程序中,加入 import pdb 和 pdb.set_trace() 这两行代码就行了

a = 1
b = 2
import pdb
pdb.set_trace()
c = 3
print(a + b + c)

这时,我们就可以执行,在 IDE 断点调试器中可以执行的一切操作,比如打印,语法是"p ":

(pdb) p a
1
(pdb) p b
2

除了打印,常见的操作还有“n”,表示继续执行代码到下一行

(pdb) n
-> print(a + b + c)

1-3Python垃圾回收及性能分析
今日安排
课程
时间:2020/06/19 20:00-22:00
老师:Amy
内容
• 实例方法名字的字符串调用方法
• 垃圾回收(上)
• 垃圾回收(下)
• 调试和性能分析
• 经典的参数错误
通过实例方法名字的字符串调用方法
我们有三个图形类 Circle,Triangle,Rectangle
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口
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

• getattr(x,“y”,None) --> 等同于 x.y 当x中不含有y时,返回None。
• map(func,iterable) --> 将iterable中的元素一一映射到func函数中处理,并且返回新的map对象。
垃圾回收机制
介绍
在Python程序运行的时候,会在内存中开辟一块空间,用于存放临时变量;当计算完成之后,就会将结果输出到永久性存储器中。如果数据量特别大,那内存空间管理不妥当的话就非常容易爆内存,程序可能直接终止。
在Python中,一切皆对象。所以,每一个变量,实际上都是对象的一个指针。所以,当这个对象的引用计数(指针数)为0的时候,说明它也变成了垃圾,需要被放到回收箱中。
OS模块
与操作系统交互的库
psutil模块
与系统交互的库,能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息。它主要用来做系统监控,性能分析,进程管理。
通过以下代码检测程序在运行时的内存消耗
import os
import psutil
def show_info(start):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss/1024./1024
print(f"{start}一共占用{memory:.2f}MB")
def func():
show_info(“initial”)
a = [i for i in range(1000000)]
show_info(“created”)
func()
show_info(“finished”)
• 当a是局部变量时,在返回到函数调用处时,局部变量的引用会注销。这时,列表a所指代对象的引用数为0,Python便会执行垃圾回收,因此之前占用的内存被收回了。
• 当a是全局变量的时,即使函数体内代码执行完毕,返回到函数调用处时,对列表a的引用仍然是存在的,所以对象不会被垃圾回收,依然占有大量内存。
Python内部的引用计数机制
我们可以通过sys.getrefcount()这个函数,来了解Python内部的引用计数机制。
import sys
a = [1,2,3]
print(sys.getrefcount(a))
• getrefcount本身也会引入一次计数。
手动启动垃圾回收
如果我们可以手动删除完对象的引用,然后强制调用gc.collect()清除没有引用的对象,其实也就是手动的启动对象的回收。
循环引用
如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?
import gc
def show_info(start):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss/1024./1024
print(f"{start}一共占用{memory:.2f}MB")
def func():
show_info(“initial”)
a = [i for i in range(100000)]
b = [i for i in range(100000)]
show_info(“after a,b created”)
# 相互引用
a.append(b)
b.append(a)
func()
gc.collect()
show_info(“finished”)
总而言之,当双向引用的时候,引用计数虽然还在,但我们可以手动拉起回收,进行释放内存。所以,引用次数是垃圾回收的 充分非必要条件。
调试内存泄漏
在Python中通过引用计数和垃圾回收来管理内存,但是在一定情况下也会产生内存泄露
• 第一是对象被另一个生命周期特别长的对象所引用
• 第二是循环引用中的对象定义了__del__函数
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/
用 pdb 进行代码调试
如何使用 pdb
首先,要启动 pdb 调试,我们只需要在程序中,加入 import pdb 和 pdb.set_trace() 这两行代码就行了
a = 1
b = 2
import pdb
pdb.set_trace()
c = 3
print(a + b + c)
这时,我们就可以执行,在 IDE 断点调试器中可以执行的一切操作,比如打印,语法是"p ":
(pdb) p a
1
(pdb) p b
2
除了打印,常见的操作还有“n”,表示继续执行代码到下一行
(pdb) n
-> print(a + b + c)
而命令l,则表示列举出当前代码行上下的 11 行源代码,方便开发者熟悉当前断点周围的代码状态

(pdb) l
1    a = 1
2    b = 2
3    import pdb
4    pdb.set_trace()
5  ->  c = 3
6    print(a + b + c)

命令“s“,就是 step into 的意思,即进入相对应的代码内部。
当然,除了这些常用命令,还有许多其他的命令可以使用
参考对应的官方文档:https://docs.python.org/3/library/pdb.html#module-pdb)

用 cProfile 进行性能分析

了要对程序进行调试,性能分析也是每个开发者的必备技能。
日常工作中,我们常常会遇到这样的问题:在线上,我发现产品的某个功能模块效率低下,延迟高,占用的资源多,但却不知道是哪里出了问题。
这时,对代码进行 profile 就显得异常重要了。
这里所谓的 profile,是指对代码的每个部分进行动态的分析,比如准确计算出每个模块消耗的时间等。
计算斐波拉契数列,运用递归思想

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

fib_seq(30)

接下来,我想要测试一下这段代码总的效率以及各个部分的效率

import cProfile

cProfile.run('fib_seq(30)')

在这里插入图片描述
参数介绍:

  • ncalls:函数被调用的次数。如果这一列有两个值,就表示有递归调用,第二个值是原生调用次数,第一个值是总调用次数。
  • tottime:函数内部消耗的总时间。(可以帮助优化)
  • percall:是tottime除以ncalls,一个函数每次调用平均消耗时间。
  • cumtime:之前所有子函数消费时间的累计和。
  • filename:lineno(function):被分析函数所在文件名、行号、函数名。
经典的参数错误
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)   

注意:

  • 列表为可变类型
    li += 1 相当于改变li本身
    li = li + 1 相当于li是两个变量 id不一致 返回的是 原本的li
  • 元组为不可变类型
    tu += 1 也就是重新创建了一个tu变量 id不一致
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值