- 实例方法的名字的字符串调用
- 垃圾回收
- 调试与性能分析
- 经典参数错误
通过实例方法名字的字符串调用方法
有三个图形类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/
)转换为图片
用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不一致