1、通过实例方法名字的字符串调用方法
我们有三个图形类
Circle,Triangle,Rectangle
# 圆,三角,矩形
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口。
In [40]: s='abc123'
In [41]: s.find('123') # 返回值是‘1’的下标
Out[41]: 3
In [42]: getattr(s, 'find') # str字符串中有find方法
Out[42]: <function str.find>
In [43]: getattr(s, 'find')('123') # 直接返回‘1’的下标值
Out[43]: 3
In [44]: getattr(s, 'pop') # str没有pop方法,list中有
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-44-b6b4c4fb0030> in <module>
----> 1 getattr(s, 'pop')
AttributeError: 'str' object has no attribute 'pop'
In [45]: getattr(s, 'pop', None) # 返回为None
In [46]: def demo(x):
...: return x ** 2
...:
In [47]: demo(2)
Out[47]: 4
In [48]: map(demo, [1,2,3])
Out[48]: <map at 0x19480932ef0>
In [49]: list(map(demo, [1,2,3])) # 强制转换成list,再分别调用demo方法
Out[49]: [1, 4, 9] # map函数是对传入的函数进行指定的映射
In [50]:
getattr函数和map函数的举例:
from 实例方法名字字符串调用_lab1 import Triangle,Rectangle,Circle
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) # 传入的shape对象中是否有name的方法
# None是为了返回None,方便下面的if判断
if f: # 假如方法名称存在
return f()
print(get_area(shape1))
print(get_area(shape2))
print(get_area(shape3))
shape_list = [shape1, shape2, shape3]
area = list(map(get_area, shape_list)) # map函数的使用
print(area)
2、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)) # 8M
def func():
show_memory_info('initial')
# global a # 若申明成全局变量,就不会触发垃圾回收机制
a = [i for i in range(10000000)] # 函数内部的局部变量
show_memory_info('after a created') # 396M
return a # 只加上返回值,内存会释放
f = func() # 若加上变量接收返回值a,内存就不会被释放
show_memory_info('finished') # 9M 内存释放了,所以内存恢复
'''输出内容
initial memory used: 8.8671875 MB
after a created memory used: 396.66796875 MB
finished memory used: 9.6015625 MB
'''
引用计数的使用:
注意:计数不是累加的,只算当前的函数引用
import sys
a = []
# 查看变量的引用计数
print(sys.getrefcount(a)) # 2 getrefcount函数也要计算一次,所以值为2
def fun(a):
print(sys.getrefcount(a))
fun(a) # 4
b = a # a的计数不是累加的,只算当前的函数引用
print(sys.getrefcount(a)) # 3
手动释放内存
del方法
import gc # 清除没有引用的对象模块
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)) # 8M
def func():
show_memory_info('initial')
# global a # 若申明成全局变量,就不会触发垃圾回收机制
a = [i for i in range(10000000)] # 函数内部的局部变量
show_memory_info('after a created') # 396M
return a # 只加上返回值,内存会释放
show_memory_info('init') # 初始是40M
a = [i for i in range(10000000)]
show_memory_info('after a created') # 427M
del a # 手动清除
gc.collect() # 清除没有引用的对象
show_memory_info('finish') # 释放了内存 40M
'''
init memory used: 40.625 MB
after a created memory used: 427.81640625 MB
finish memory used: 40.921875 MB
'''
循环引用
如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?
引用次数为0的时候,垃圾回收启动的充分必要条件?
答:不是,是充分非必要条件
# 引用次数为0的时候,垃圾回收启动的充分必要条件? 不是,充分非必要条件
# 循环引用
import os
import psutil
import gc # 清除没有引用的对象模块
# 显示当前 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)]
b = [i for i in range(10000000)]
show_memory_info('after a, b created')
a.append(b)
b.append(a) # 循环引用,互相引用
func()
gc.collect() # 手动清除之后,内存被释放 9.79296875 MB
show_memory_info('finished') # 没有被释放内存
'''
initial memory used: 8.9375 MB
after a, b created memory used: 784.41015625 MB
finished memory used: 784.4140625 MB
'''
调试内存泄漏
虽然有了自动回收机制,但这也不是万能的,难免还是会有漏网之鱼。内存泄漏是我们不想见到的,而且还会严重影响性能。有没有什么好的调试手段呢?
就是 objgraph,一个非常好用的可视化引用关系的包。主要推荐两个函数,第一个是 show_refs(),它可以生成清晰的引用关系图。
import objgraph
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
objgraph.show_refs([a])
dot转图片:dot转成图片格式
总结
- 垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;
- 引用计数是其中最简单的实现,不过切记,这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收;
- Python的自动回收算法包括标记清除和分代收集,主要针对的是循环引用的垃圾收集;
- 调试内存泄漏方面, objgraph 是很好的可视化分析工具。
3、调试和性能分析
在程序中相应的地方打印,的确是调试程序的一个常用手段,但这只适用于小型程序。如果程序不大,每次运行都非常快,那么使用 print(),的确是很方便的。
用 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 的意思,即进入相对应的代码内部。
执行完成之后继续输入n,会报错:如上图所示。
除了这些常用命令,还有许多其他的命令可以使用
参考对应的官方文档:pdb命令官方文档
用 cProfile 进行性能分析
日常工作中,我们遇到这样的问题:在线上,我发现产品的某个功能模块效率低下,延迟高,占用的资源多,但却不知道是哪里出了问题。
这时,对代码进行 profile 就显得异常重要了。
这里所谓的 profile,是指对代码的每个部分进行动态的分析,比如准确计算出每个模块消耗的时间等。
计算斐波拉契数列,运用递归思想
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)') # 注意分号
输出如下显示
参数介绍:
- ncalls,是指相应代码 / 函数被调用的次数
- tottime,是指对应代码 / 函数总共执行所需要的时间(注意,并不包括它调用的其他代码 / 函数的执行时间)
- tottime percall,就是上述两者相除的结果,也就是tottime / ncalls
- cumtime,则是指对应代码 / 函数总共执行所需要的时间,这里包括了它调用的其他代码 / 函数的执行时间
- cumtime percall,则是 cumtime 和 ncalls 相除的平均结果。
4、经典的参数错误
python的类型分为 可变 和 不可变 (指的是内存地址会不会改变)
可变: 列表和字典
不可变: 数组、字符串、元祖
def add(a, b):
a += b
return a
a = 1
b = 2
c = add(a, b)
print(c) # 3
print(a, b) # 1 2
a = [1, 2]
b = [3, 4]
c = add(a, b)
print(c) # [1, 2, 3, 4]
print(a, b) # a=[1, 2, 3, 4] b=[3, 4]
a = (1, 2)
b = (3, 4)
c = add(a, b)
print(c) # (1, 2, 3, 4)
print(a, b) # a=(1, 2) b=(3, 4)
不可变类型
以int类型为例:实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int对象,i引用自这个新的对象。
可变类型
以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改。
小整数对象池
-5——256
在-5到256之间的数,python是已经创建好的对象,超出这个范围就不是,需要单独创建。
# 这是交互环境,不同于pycharm的运行环境
In [1]: a= 1
In [2]: b = 1
In [3]: a is b
Out[3]: True
In [4]: a==b
Out[4]: True
In [5]: a==2222
Out[5]: False
In [6]: a=2222
In [7]: b=2222
In [8]: a is b
Out[8]: False
In [9]: a == b
Out[9]: True