本文共 3271 字,大约阅读时间需要 10 分钟。
前些日子用python基于prometheus开发了一个vsphere volume卷监控的exporter,于是跟vsphere的api(pyvmomi)接口打上了交道,开发的过程中你会发现pyvmomi的接口返回的对象好多列表类型的,当你取其中一个对象的时候可能需要进行多层的循环遍历。于是促使了我写这一篇文章,记录一下在使用python搬砖过程中的一些心得体会。如有错误,欢迎大家指正。
Python里面所谓高质量的代码,我自己理解的主要是两方面。一是编写具有python风格的代码,即所谓的Pythonic;二是代码的执行效率。Python的执行效率一直被人诟病,这点我承认,但我更认同的一种说法是“编程语言本身没有好坏,关键在于使用者的使用方法是否恰当。”
以下是个人总结的,在python编程过程中常见的几点提高代码质量的方法:
变量的赋值
1 2 3 4 5 6 7 8 9 10 | In [11]: a, b = 10, 50 # 赋值写在一行 In [12]: a Out[12]: 10 In [13]: b Out[13]: 50 In [14]: a, b = b, a # a, b互换 In [15]: a Out[15]: 50 In [16]: b Out[16]: 10 |
变量交换的时候尽量避免使用中间变量增加开销。
2. 列表推导提高效率和可读性
如下生成一个新的列表:
1 2 | In [17]: [n for n in range(10)] Out[17]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |
另一方面,列表推导也可能被滥用。通常的原则是,只用列表推导来创建新的列表,并且尽量保持简短。 如果列表推导的代码超过了两行,你可能就要考虑是不是得用 for 循环重写了。就跟写文章一样,并没有什么硬性的规则,这个度需要自己把握。
3. 列表和字典的迭代
列表使用enumerate() 获取list的索引和值,字典使用iteritems方法获取索引和值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | In [18]: l1 = [n for n in range(10)] In [21]: for k, v in enumerate(l1): ....: print k, v ....: 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 In [23]: dict1 = { 'a' :1, 'b' :2, 'c' :3, 'd' :4} In [24]: dict1 Out[24]: { 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4} In [25]: for k, v in dict1.iteritems(): ....: print k, v ....: a 1 c 3 b 2 d 4 |
4. 使用三元表达式进行条件赋值
三元表达式允许用简单的一行快速判断,而不是使用复杂的多行if语句,可以使代码简单、可维护。
1 2 3 4 | In [26]: 1 if 5>3 else 0 Out[26]: 1 In [27]: 1 if 5>8 else 0 Out[27]: 0 |
举一个在实际生产中运用列表推导和三元表达式结合使用的例子:
1 2 | dc_list = [datacenter for datacenter in root_folder.childEntity if isinstance( datacenter, vim.Datacenter)] |
这里生成了一个名为dc_list的列表,首先在"root_folder.childEntity"中遍历出datacenter,接着判断这个datacenter是否是一个"vim.Datacenter"的实例,如果为真,加入到dc_list列表中,最终返回该datacenter列表。
5. 使用 with 自动关闭资源
对文件操作完成后应该立即关闭它们,因为打开的文件不仅会占用系统资源,而且可能影响其他程序或者进程的操作,甚至会导致用户期望与实际操作结果不一致。
1 2 3 4 5 6 7 | In [5]: with open ( '111.py' , 'rb' ) as file : ...: for line in file : ...: print line ...: #!/usr/bin/env python # -*- coding:utf-8 -*- print "name is %s" % __file__ |
6. 使用yield
这里有一个生成斐波那契数列的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | In [8]: def fab(n): ...: a, b = 0, 1 ...: for i in xrange(n): ...: yield b ...: a, b = b, a + b ...: In [9]: fab(20) Out[9]: <generator object fab at 0x1092975a0> In [10]: for n in fab(20): ....: print n ....: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 |
可以看出一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
7. 减少循环内部执行的计算
优化python循环的关键一点,是要减少Python在循环内部执行的工作量。
1 2 3 4 5 6 | In [30]: a = range(10000) In [31]: size_a = len(a) In [32]: %timeit -n 1000 for i in a: k = len(a) 1000 loops, best of 3: 658 μs per loop In [33]: %timeit -n 1000 for i in a: k = size_a 1000 loops, best of 3: 304 μs per loop |
8. 字符串连接优先使用"join",而不是“+”
1 2 3 | In [42]: letter = [ 'a' , 'b' , 'c' , 'd' ] In [43]: print '' . join (letter) abcd |
9. None类型判断
不要使用‘==’ None的形式:
1 2 | if foo == None: do_something() |
正确用法:
1 2 | if not foo: do_something() |
10. “过早的优化是万恶之源”
最后不得不提一下这句话,借用一下别人的诠释:
1 2 3 4 | Make it Work. Make it Right. Make it Fast. 不要跳过前面两个直奔第三个! |