python高级编程技巧之垃圾回收及性能分析2020-07-06

  • 实例方法的名字的字符串调用
  • 垃圾回收
  • 调试与性能分析
  • 经典参数错误

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

有三个图形类Circle,Triangle,Rectangle,每个类里面都有一个面积计算方法,但是每个面积计算方法的名字不相同,使用每种方法名调用对应的接口

from math import pi

class Rectangle(object):
    def __init__(self, h, w):
        self.h, self.w = h, w

    def get_area(self):
        return self.h * self.w

class Triangle(object):
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    def area(self):
        p = (self.a + self.b + self.c) / 2
        return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5

class Circle(object):
    def __init__(self, r):
        self.r = r

    def get_Area(self):
        return pi * (self.r**2)


def Area(shape):
    li = ['get_area', 'area', 'get_Area']
    for list_Name in li:
        f = getattr(shape, list_Name, None)   # 等价于shape.list_Name
        if f:
            return f()

shape1 = Circle(2)
shape2 = Rectangle(2, 4)
shape3 = Triangle(3, 4, 5)
class_name = [shape1, shape2, shape3]
a = list(map(Area, class_name))
print(a)  
  • getattr(x, 'y', None) 等价于x.y 如果类x中没有y方法,则返回None
  • map(func,interable) 将可迭代映射到func中进行处理,返回的是一个对象,需要强制转换

垃圾回收机制

介绍

当python程序开始运行的时候,会开辟一块存储空间,用于存储临时变量,当程序运行出结果的时候再保存在永久存储器当中。当数据量特别大的时候,如果管理不好内存,内存就会爆掉,程序就会终止。

在python中一切皆对象。所以每一个变量都是一个对象的指针。当引用计数(指针数)为0的时候,就会变成垃圾,需要清理。

os模块

与操作系统交互的库

psutil模块

与系统交互的库,能轻松获取系统运行的进程和系统的利用率(包括CPU、内存、磁盘、网络等)。他主要是做系统监控、性能分析、进程管理。

  • 通过以下代码检测系统在运行时的消耗
import os
import psutil

def show_info(start):
    # 获取当前进程id
    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(10000)]
    show_info('created')
    
func()
show_info('finished')
  • Python内部的引用计数机制
import sys

a = [1, 2, 3]                # 第一次
print(sys.getrefcount(a))    # 第二次

def func(a):   # 第三次
    # 四次:
    print(sys.getrefcount(a))
    
func(a)  # 第四次

注意:sys.getrefcount(a)重复调用只会计算一次不会累加

手动启动垃圾回收

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

import sys
import gc

a = [1, 2, 3]
print(sys.getrefcount(a))

del a  # 相当于把对象的引用删除,但是对象没有被回收
gc.collect()  # 回收对象
print(a)

循环引用

如果有两个对象互相引用,并且不再被其他对象引用,那么应该被回收吗?

import os
import psutil
import gc

def show_info(start):
    # 获取当前进程id
    pid = os.getpid()

    # 获取当前堆成对象
    p = psutil.Process()

    # 获取当前对象内存消耗
    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(10000)]
    b = [i for i in range(10000)]
    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文件,通过连接(<meta name="source" content="lake">https://onlineconvertfree.com/
)转换为图片

image.png

用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不一致
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341