改善Python程序的建议总结
程序员文章站
2022-07-14 13:58:22
...
1.格式化输出
- 一般情况: print ‘hello %s!’ % (‘Tom’)
- 特多时候: print ‘Hello %(name)s!’ % {‘name’:’Tom’}
- 官方推荐: str.format()方法
eg:print ‘{greet} from {language}.’.format(greet = “hello world”, language = ‘python’)
2.模块和包
- 包和模块的命名采用小写,单数形式,而且短小
- 包通常仅作为命名空间,如只包含空的init.py文件
3.常用方法:
- 三目运算 eg: x if C else Y 替换–>其他语言C ? X:Y
- switch…case 应该用下面两种的代替
1.用if elif else
2.def f(x):
return {
0:"you typed zero.\n",
1:"you are in top.\n",
2:"n is an even number\n"
}.get(x,"Only single-digit numbers are allowed\n")
4.注释的使用
注意添加文档注释:3对”—> 注释要清楚地描述方法的功能,并对参数,返回值以及可能发生的异常进行说明
推荐在文件头包含copyright 声明,模块的描述等,如有必要,可以考虑加入作者的信息及变更记录
like:
"""
Licensed Materials - Property of CorpA
(C) Copyright A Corp.1999, 2011 All Rights Reserved
CopyRight statement and purpose....
------------------------------------------------------
File Name : comments.py
Description: description what the main function of this file
Author: LF
change Activity:
list the change activity and time and author information
-----------------------------------------------------------
"""
5.函数的使用
- 合理的参数设定 参数数量不应过多 同时要考虑向下兼容
eg:前一个 def readfile(filename) 版本升级添加日志 def readfile(filename,logger)
—> 应用缺省参数 def readfile(filename,logger=xxx) - 一个函数只做一件事
- 使用异常处理返回错误,保证通过单元测试等
6.常量集中到一个文件
- 常量的命名 一般是全大写字母 中间用下划线连接 这只是一直约定俗成的风格
- 通过自定义的类实现常量功能 —>要求:命名全部大写 和 值一旦绑定不可修改
eg:文件”常量”text
7.利用assert 语句来发现问题(断言) 基本语法assert expression1 [“,”expression2]
- x =1 y =2 assert x==y, ‘not equals’ ====> 报异常
8.数据交换不要使用中间变量 尽量使用x,y=y,x
9.充分利用Lazy evaluation 的特性
避免不必要的计算 eg:if x or y 当x为 true时就直接返回 不用计算y
if x and y false ——— 像这种就是利用了lazy节省空间,使用无限循环的数据结构成为可能
eg:生成器表达式 a= (x for x in range(100))—>不会立即生成数据列表 用的时候才会生成 print(a[9])
10.理解”枚举替代实现”的缺陷 待看…..Enum
11.不推荐使用type()来进行类型检查
- 基于内建类型扩展的用户自定义类型 type并不能准确返回结果 比如class A(int)
- 应使用 isinstance(xx,int)来检查
12.尽量转换为浮点类型后再做除法
- py3不需要注意
13.警惕eval()的安全漏洞
- eval()是将字符串改变为有效的表达式 “eval is evil” (eval是邪恶的)
14.使用enumerate()获取序列迭代
- eg: for i,value in enumerate(list_)
15.区分 is 和 == —>
- python中 字符串驻留机制,小整数的定义是[-5,257)这些整数对象是提前建立好的;同理,单个字母也是这样的.
两个对象指向的内存地址是一样的,官方设计的
eg:a=’hi’ b=’hi’ id(a)==id(b) - is: 是检查对象标示符 也就判断id(xx)的值是否一样 即内存地址是否一致
- ==: 是检查对象的值
16.考虑兼容性,尽可能使用unicode —>二进制
- 一般就是向文件传输时 先编码 再到达客户端时解码 a=”你好”.encode(“utf-8”) –> a.decode(“utf-8”)
- encode和decode中参数 window 一般是gbk liunx等其他一般是utf-8
17.构建合理的包层次来管理module —->待详看
18.有节制地使用from….import语句
- 一般情况优先使用import a —> print a.xxx
- 有节制地使用from a import xxx —> print xxx
- 尽量避免使用from a import * —>污染命名空间
19.i+=1 不等于 ++i
- 在python中 ++i 不是自增 +(+1) 即正数取正
- 同理:–i 不是自减 -(-1) 即负数取反
20.使用with自动关闭资源
- 基本上只要是需要close的都可以用with eg:
with open(xx_file_name,'rb') as f:
f.read()
- 实际上任何实现了上下文协议的对象的都可以称为一个上下文管理器->如 打开/关闭文件,异常处理,断开流的连接,锁分配等
21.多使用else 让程序变的更加pythonic
- eg: try-except-else-finally
22.异常处理注意点
- 注意异常粒度,不推荐在try中放入过多的代码 最好保持异常粒度的一致性和合理性
- 异常捕获尽量具体指定,少用Except 使用单独的except 最好能够使用raise将异常抛出
- 注意异常捕获的顺序 (…)
- 使用更为友好的异常信息,遵守异常参数的规范
23.避免finally中可能发生的陷阱
- finally中产生新的异常或者执行了return or break 那么临时报错的异常就会丢失,导致异常屏蔽
24.深入理解None,正确判断对象是否为空
python 以下数据会当做空来处理:
- 常量None 常量False
- 任何形式的数值类型0 eg:0,0L ,0.0,0j
- 空的序列字典,如”(),[],{}
- 当用户定义的类中定义了nonzero()和len()方法,并且该方法返回bool值false或者整数0
常量None的特殊性体现在它 既不是0orFalse,它就是一个空值对象,数据类型为NoneType ,遵循单例模式
None与任何其他非None的对象比较结果为False None !={}
一般判断空列表 直接 if list or if len(list)
25.连接字符串应优先使用join而不是+ —-> 字符串规模比较大的时候
26.python 格式化字符串尽量使用 str.format 而不是%
- eg: ‘xxxx{0}xxxx{1}’.format(n1,n2)
最直接的理由:% 最终会被.format 方式代替
27.区别对待可变对象和不可变对象
- 不可变对象: 数字,字符串,元组
- 可变对象 : 字典,列表,字节数
- 注意:列表的切片操作相当于浅拷贝 —>list1=[1,2,3] list2=list1[:] id(list1)!=id(list2)
28.使用列表解析 like [x for x in range(10)]
- 代码更简洁
- 效率更高 但是大数据不是最佳的选择,过多的内存消耗可能会导致memoryError
29.警惕默认参数潜在的问题
- def xxx(xx,a=[]) 不要使用列表等可变类型 def实际是可执行语句 默认参数也会被计算
===>应修改为 def xxx(xx,a=None)
30.慎用变长参数
- 使用过于灵活,可能破坏程序的健壮性
- 如果一个函数的参数列表很长,虽然可以通过使用*args 和 **kwargs 来简化函数的定义
但通常这意味着这个函数可以有更好的实现方式,应该被重构,用一个序列来保存需要传递的参数
适合场景是: 装饰器 读取配置文件
31.深入理解str()和repr()的区别
- str()主要面向用户,目的是可读性,返回可读性强的字符串类型
repr()面向的是Python解释器or 开发人员,返回值表示python解释器内部的含义 - 在解释器中直接输入a时默认调用repr()函数,而print a 则 调用的是str()
- 一般来说类中毒定义了
__repr__()
方法,而__str__()
则为可选,没有__str__()
,则默认会使用__repr__()
32.分清staticmethod和classmethod的适用场景
- 静态方法:实现功能不和实例相关也不和类相关的独立方法,如一些字符验证等,定义在类中能有效将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性;当然,如果有一组独立的方法,将其定义在一个模块中,通过模块来访问这些方法也是不错的选择
- 类方法: 减少子类的代码重写, 降低代码的冗余
33.字符串的基本用法
- 多行字符串使用下面的方式
s = (
'aaaaa'
'bbbb'
'ccccc'
)
- 3对双引号会把换行符和前导空格当做字符串的一部分
- str.startswith和endswith中匹配参数可以使用元组,有一个匹配上就返回T
- 判断字符串包含子串的判断推荐使用 in 和 not in
''.split() ==> [] ''.split(' ') ==> ['']
34.按需选择sort()或者sorted()
- sorted()会保留原有列表生成新的列表
- sorted功能非常强大,而sort只能排一般的列表
35.使用Counter 进行计数统计
from collections import Counter
some_data = ['a','2',2,4,5,'2','b',4,7,'a',5,'d','a','z']
print Counter(some_data)
>>>Counter({'a': 3, '2': 2, 4: 2, 5: 2, 2: 1, 'b': 1, 7: 1, 'd': 1, 'z': 1})
- Counter类属于字典类的子类,是一个容器对象,主要用来统计散列对象.
# 取值
xx = Counter(some_data)
print xx.['a']
单例模式
- 保证系统中一个类只有一个实例而且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源
class Test(object):
is_instance = None
is_first = True
def __new__(cls, *args, **kwargs):
if cls.is_instance == None:
cls.is_instance = object.__new__(cls)
return cls.is_instance
def __init__(self, name):
if Test.is_first:
self.name = name
Test.is_first = False
test1 = Test('lf1')
print(id(test1)) # 2136967041656
print(test1.name) # lf1
test2 = Test('lf2')
print(id(test2)) # 2136967041656
print(test2.name) # lf1
用mixin 模式让程序更加灵活(没懂….)
用发布订阅模式实现松耦合
发布订阅模式(publish/subscribe)是一种编程模式,
消息的发送者将消息分为不同的类别直接发布,并不关注订阅者是谁;
订阅者直接接收感兴趣的消息,并不关注发布者是谁;
优点是 发布者 与 订阅者松散的耦合,双方不需要知道对方的存在.
* 简单例子
## Broker.py
from collections import defaultdict
route_table = defaultdict(list)
def sub(topic, callback):
if callback in route_table[topic]:
return
route_table[topic].append(callback)
def pub(topic, *a, **kw):
for func in route_table[topic]:
func(*a, **kw)
## main.py
import Broker
def greeting(name):
print('hello, %s.' % name)
Broker.sub('greet', greeting) # 发布者
Broker.pub('greet', 'LaiYonghao') # 订阅者
- 安装blinker 和 python-message 两个模块的实现要完备得多
用状态模式美化代码–待理解
理解MRO与多继承 —待理解经典类和新式类区别
自己做一个模块,上传到pip中支持在线升级
了解代码优化的基本原则 8/2法则
- 优先保证代码是可工作的,过早优化是编程中一切”罪恶”的根源.
- 权衡优化的代价
- 定义性能指标,集中力量解决首要问题.针对客户关心的问题进行主次排列
- 不要忽略可读性,即不要炫技
利用cProfile 定位性能瓶颈
程序运行慢的原因有很多,但真正的原因往往是一两段设计并不那么良好的不起眼的程序.程序性能影响往往符合8/2法则,即20%的代码运行时间占用了80%的总运行时间(实际上,比例要夸张得多,通常是几十行代码占用了95%以上的运行时间)
profile是python的标准库,可以统计程序里每一函数的运行时间,并提供了多样的报表,cprofile则是它的C实现版本,剖析过程本身需要消耗的资源更少.
## test_cprofile.py
import time
def foo():
sum = 0
for i in range(100):
sum += i
time.sleep(.5)
return sum
if __name__ == "__main__":
foo()
现在使用profile分析这个程序.
if __name__ =="__main__":
import cProfile
cProfile.run("foo()")
输出如下
第二种方式:python -m cProfile test_cprofile.py
- cProfile 的统计结果以及各项意义
统计项 | 意义 |
---|---|
ncalls | 函数的调用次数 |
tottime | 函数总计运行时间,不含调用的函数运行时间 |
percall | 函数运行一次的平均时间,等于tottime/ncalls |
cumtime | 函数总计运行时间,含调用的函数运行时间 |
percall | 函数运行一次的平均时间,等于cumtime/ncalls |
filename:lineno(function) | 函数所在的文件名,函数的行号,函数名 |
将cProfile的输出保存到文件,并以各种形式来查看结果.
1.使用cProfile.run()函数再提供一个实参,就是保存输出的文件名;同样,在命令行参数里,多一个参数,用来保存cProfile的输出
2.通过pstats模块的另一个类Stats来解决.
# ....略
if __name__ == "__main__":
import cProfile
cProfile.run("foo()", "prof.txt")
import pstats
p = pstats.Stats("prof.txt")
# sort_stats(key,[...]) 以key排序 print_stats()输出结果
p.sort_stats("time").print_stats()
sort_stats的key
参数 | 参数的意义 |
---|---|
ncalls | 被调用次数 |
cumulative | 函数运行的总时间 |
file | 文件名 |
module | 模块名 |
pcalls | 简单调用统计(兼容旧版,未统计递归调用) |
line | 行号 |
name | 函数名 |
nfl | Name,file,line |
stdname | 标准函数名 |
time | 函数内部运行时间(不计调用子函数的时间) |
掌握循环优化的基本技巧
- 减少循环内部的计算.
# 1
for i in range(iter):
d = math.sqrt(y)
j += i*d
# 2 比第一种快 40%~60%
d = math.sqrt(y)
for i in range(iter):
j += i*d
- 将显式循环改为隐式循环.
"""求等差数列1,2,3,...n的和"""
# 1
sum = 0
n = 10
for i in range(n+1):
sum = sum + i
print(sum)
# 2 直接用数学知识 n*(n+1)/2 负面影响就是牺牲了代码的可读性 需添加清晰和恰当的注释是非常必要的
n = 10
print(n*(n+1)/2)
- 在循环中尽量引用局部变量.在python命名空间中局部变量优先搜索,因此局部变量的查询会比全局变量要快.
# 1
x = [10,34,56,78]
def f(x):
for i in range(len(x)):
x[i] = math.sin(x[i])
return x
# 2 性能提高10%~15%
def g(x):
loc_sin = math.sin
for i in range(len(x)):
x[i] = loc_sin(x[i])
return x
- 关注内层嵌套循环,尽量将内层循环的计算往上层移,减少内层的计算
# 1
for i in range(len(v1)):
for j in range(len(v2)):
x = v1p[i]+ v2[j]
# 2
for i in range(len(v1)):
vli = v1[i]
for j in range(len(v2)):
x = vli + v2[j]
使用生成器提高效率
使用multiprocessing克服GIL的缺陷
使用c/c++ 模块扩展提高性能
使用Cython 编写扩展模块
编写高质量代码 改善Python程序的91个建议 读后总结(上述代码为python3实现)
上一篇: SI疾病传播模型实现