一、编程前言
建议1:理解Pythonic概念
详见Python中的《Python之禅》。
Pythonic : https://www.cnblogs.com/HacTF/p/8142374.html
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的
"""python实现快速排序"""
def quicksort(array):
less = [];
greater = [];
if len(array) <= 1:
return array
pivot = array.pop()
for x in array:
if x <= pivot:
less.append(x)
else:
greater.append(x)
return quicksort(less) + [pivot] + quicksort(greater)
Pythonic的代码风格
- 交换两个变量 – 直接交换,无需引入第三变量
a, b = b, a
- python可以灵活的使用迭代器,安全的关闭文件描述符
for i in alist:
do_sth_with(i)
with open(path, 'r') as f:
do_sth_with(f)
- 不应过分使用技巧
a = [1, 2, 3, 4]
b = 'abcdef'
print a[::-1]
print b[::-1]
使用Python比较多的人可以比较容易看出其作用,就是输出逆序的a和b,但还是比较晦涩,Pythonic追求的是充分利用Python语法,上面代码可写为:
a = [1, 2, 3, 4]
b = 'abcdef'
print list(reversed(a))
print list(reversed(b))
- 字符串格式化
我们很多人一般这样写,比较简洁:
1 name = 'Tom'
2 age = '20'
3 print 'Hello %s, your age is %s !' % (name, age)
其实,%s是比较影响可读性的,尤其是数量多了之后,很难清楚哪个占位符对应哪个实参,比较Pythonic的代码如下:
1 value = {'name': 'Tom', 'age': '20'}
2 print 'Hello %(name)s, your age is %(age)s !' % value
使用%占位符的形式,依旧不是Python最推荐的,最具Pythonic风格的代码如下:
1 print 'Hello {name}, your age is {age} !'.format(name = 'Tom', age = '20')
str.format()是Python最为推荐的字符串格式化方法;
1 print f"Hello {name}, your age is {age}!"
- 包和模块
包和模块的命名采用小写、单数形式,而且短小;
包通常仅作为命名空间,可以只包含空的__init__.py文件;
- 过多的if…elif…eilf…else 应使用字典来实现
1 if n == 0:
2 print "You typed zero..."
3 elif n == 1:
4 print "You are in top..."
5 elif n == 2:
6 print "n is even number..."
7 else:
8 print "Default value"
9
10 # 使用字典来实现更好一些
11
12 def f(x):
13 return {
14 0: "You typed zero...",
15 1: "You are in top...",
16 2: "n is even number...",
17 }.get(x, "Default value")
建议2:编写Pythonic代码的建议。
-
避免劣化代码
(1) 避免只用大小写来区分不同的对象
(2) 避免使用容易引起混淆的名称,变量名应与所解决的问题域一致;
(3)不要害怕过长的变量名,有时候长的变量名会使代码更加具有可读性; -
代码中添加适当注释
(1) 行注释仅注释复杂的操作、算法,难理解的技巧,或不够一目了然的代码;
(2) 注释和代码要隔开一定的距离,无论是行注释还是块注释;
(3) 给外部可访问的函数和方法(无论是否简单)添加文档注释,注释要清楚地描述 方法的功能,并对参数,返回值,以及可能发生的异常进行说明,使得外部调用的人仅看docstring就能正确使用;
(4) 推荐在文件头中包含copyright申明,模块描述等;
(5) 注释应该是用来解释代码的功能,原因,及想法的,不该对代码本身进行解释;
(6) 对不再需要的代码应该将其删除,而不是将其注释掉; -
适当添加空行使代码布局更为优雅、合理
(1) 在一组代码表达完一个完整的思路之后,应该用空白行进行间隔,推荐在函数定义或者类定义之间空两行,在类定义与第一个方法之间,或需要进行语义分隔的地方空一行,空行是在不隔断代码之间的内在联系的基础上插入的;
(2) 尽量保证上下文语义的易理解性,一般是调用者在上,被调用者在下;
(3) 避免过长的代码行,每行最好不要超过80字符;
(4) 不要为了保持水平对齐而使用多余的空格; -
编写函数的几个原则
(1) 函数设计要尽量短小,嵌套层次不宜过深;
(2) 函数申明应做到合理、简单、易于使用,函数名应能正确反映函数大体功能,参数设计应简洁明了,参数个数不宜过多;
(3) 函数参数设计应考虑向下兼容;
(4) 一个函数只做一件事,尽量保证函数语句粒度的一致性; -
将常量集中到一个文件
Python没有提供定义常量的直接方式,一般有两种方法来使用常量;
(1) 通过命名风格来提醒使用者该变量代表的意义为常量,如常量名所有字母大写,用下划线连接各个单词,如MAX_NUMBER,TOTLE等;
(2) 通过自定义的类实现常量功能,常量要求符合两点,一是命名必须全部为大写字母,二是值一旦绑定便不可再修改;
建议3:理解Python与C的不同之处
比如缩进与{},单引号双引号,三元操作符?,Switch-Case语句等。
这里有一篇文章介绍:(转载)https://blog.youkuaiyun.com/Kancollection/article/details/83144832
建议4:深入学习Python相关知识
比如语言特性、库特性等,比如Python演变过程等,深入学习一两个业内公认的Pythonic的代码库,比如Flask等;
二、编程惯用法
建议5:利用assert语句来发现问题,但要注意,断言assert会影响效率。
建议6:数据交换值时不推荐使用临时变量,而是直接a, b = b, a。
建议7:充分利用惰性计算(Lazy evaluation)的特性,从而避免不必要的计算。
惰性计算 :
1、避免不必要的计算,带来性能上的提升,
比如 if x and y,if x or y,如果对于or条件表达式,应该将值为真可能性较高的变量写在or的前面,而and则应该推后
2、节省空间,使得无限循环的数据结构成为可能,
比如生成器,仅在每次需要计算的时候才通过yield产生所需要的元素
from itertools import islice
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
print(list(islice(fib(),5)))
结果:[0, 1, 1, 2, 3]
"""itertools.islice() --对迭代器做切片操作"""
def count(n):
while True:
yield n
n += 1
c = count(0)
# 生成器的绝妙之处:它只会在迭代时才会运行,所以死循环也没有问题,返回一个generator
# print(c[:]) # TypeError: 'generator' object is not subscriptable
import itertools
for x in itertools.islice(c, 10, 13):
print(x)
Outs:
10
11
12
1、islice()产生的结果是一个迭代器,它可以产生出所需要的切片元素,但这是通过访问并丢弃所有起始索引之前的元素来实现的;之后的元素会由islice对象产生出来,直到到达结束索引为止;
2、islice()会消耗掉所提供的迭代器中的数据,由于迭代器中的元素只能访问一次,所有如果之后还需要去访问,那就应该先将数据转到列表中去。
yield函数介绍:https://blog.youkuaiyun.com/mieleizhi0522/article/details/82142856/
建议8:理解枚举替代实现的缺陷(最新版Python中已经加入了枚举特性)。
** 使用普通类直接实现枚举
在Python中,枚举和我们在对象中定义的类变量时一样的,每一个类变量就是一个枚举项,访问枚举项的方式为:类名加上类变量,像下面这样:**
class color():
YELLOW = 1
RED = 2
GREEN = 3
PINK = 4
# 访问枚举项
print(color.YELLOW) # 1
# 虽然这样是可以解决问题的,但是并不严谨,也不怎么安全,比如:
# 1、枚举类中,不应该存在key相同的枚举项(类变量)
# 2、不允许在类外直接修改枚举项的值
class color():
YELLOW = 1
YELLOW = 3 # 注意这里又将YELLOW赋值为3,会覆盖前面的1
RED = 2
GREEN = 3
PINK = 4
# 访问枚举项
print(color.YELLOW) # 3
# 但是可以在外部修改定义的枚举项的值,这是不应该发生的
color.YELLOW = 99
print(color.YELLOW) # 99
# 解决方案:使用enum模块
# enum模块是系统内置模块,可以直接使用import导入,但是在导入的时候,不建议使用import enum将enum模块中的所有数据都导入,一般使用的最多的就是enum模块中的Enum、IntEnum、unique这几项
# 导入枚举类
from enum import Enum
# 继承枚举类
class color(Enum):
YELLOW = 1
BEOWN = 1
# 注意BROWN的值和YELLOW的值相同,这是允许的,此时的BROWN相当于YELLOW的别名
RED = 2
GREEN = 3
PINK = 4
class color2(Enum):
YELLOW = 1
RED = 2
GREEN = 3
PINK = 4
# 使用自己定义的枚举类:
print(color.YELLOW) # color.YELLOW
print(type(color.YELLOW)) # <enum 'color'>
print(color.YELLOW.value) # 1
print(type(color.YELLOW.value)) # <class 'int'>
print(color.YELLOW == 1) # False
print(color.YELLOW.value == 1) # True
print(color.YELLOW == color.YELLOW) # True
print(color.YELLOW == color2.YELLOW) # False
print(color.YELLOW is color2.YELLOW) # False
print(color.YELLOW is color.YELLOW) # True
print(color(1)) # color.YELLOW
print(type(color(1))) # <enum 'color'>
注意事项如下:
1、枚举类不能用来实例化对象
2、访问枚举类中的某一项,直接使用类名访问加上要访问的项即可,比如 color.YELLOW
3、枚举类里面定义的Key = Value,在类外部不能修改Value值,也就是说下面这个做法是错误的
color.YELLOW = 2 # Wrong, can't reassign member
4、枚举项可以用来比较,使用==,或者is
5、导入Enum之后,一个枚举类中的Key和Value,Key不能相同,Value可以相,但是Value相同的各项Key都会当做别名,
6、如果要枚举类中的Value只能是整型数字,那么,可以导入IntEnum,然后继承IntEnum即可,注意,此时,如果value为字符串的数字,也不会报错:
from enum import IntEnum
7、如果要枚举类中的key也不能相同,那么在导入Enum的同时,需要导入unique函数
from enum import Enum, unique
建议9:不推荐使用type来进行类型检查,因为有些时候type的结果并不一定可靠。如果有需求,建议使用isinstance函数来代替。
建议10:尽量将变量转化为浮点类型后再做除法(Python3以后不用考虑)。
建议11:警惕**eval()**函数的安全漏洞,有点类似于SQL注入。
关于解析eval()安全漏洞问题的原文链接:
[添加链接描述](https://blog.csdn.net/mingtiannihaoabc/article/details/103193116)
'''Python中eval()函数将字符串str当成有效的表达式来求值并返回计算结果。其函数声明如下:'''
eval(expression[, globals[, locals]])
- expression – 表达式
- globals – 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
- locals – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
其中,globals 参数为字典形式,locals 为任何映射对象,它们分别表示全局和局部命名空间。
如果传人globals参数的字典中缺少__builtins__的时候,当前的全局命名空间将作为globals参数输人并且在表达式计算之前被解析。
locals 参数默认与globals相同,如果两者都省略的话,表达式将在eval()调用的环境中执行。
建议12:使用enumerate()同时获取序列迭代的索引和值。
建议13:分清 == 和 is 的适用场景,特别是在比较字符串等不可变类型变量时。
is 比较对象的标识符 id(obj)
== 判断值是否相等
建议14:尽量使用Unicode。在Python2中编码是很让人头痛的一件事,但Python3就不用过多考虑了。
建议15:构建合理的包层次来管理Module。
三、基础用法
建议16:有节制的使用from…import语句,防止污染命名空间。
建议17:优先使用absolute import来导入模块(Python3中已经移除了relative import)。
建议18:i+=1不等于++i,在Python中,++i前边的加号仅表示正,不表示操作。
建议19:习惯使用with自动关闭资源,特别是在文件读写中。
建议20:使用else子句简化循环(异常处理)。
建议21:遵循异常处理的几点基本原则。
(1)注意异常的粒度,try块中尽量少写代码;
(2)谨慎使用单独的except语句,或except Exception语句,而是定位到具体异常;
(3)注意异常捕获的顺序,在合适的层次处理异常;
(4)使用更加友好的异常信息,遵守异常参数的规范;
建议22:避免finally中可能发生的陷阱。
建议23:深入理解None,正确判断对象是否为空。
建议24:连接字符串应优先使用join函数,而不是+操作。
建议25:格式化字符串时尽量使用.format函数,而不是%形式。
建议26:区别对待可变对象和不可变对象,特别是作为函数参数时。
建议27:[], {}和():一致的容器初始化形式。使用列表解析可以使代码更清晰,同时效率更高。
建议28:函数传参数,既不是传值也不是传引用,而是传对象或者说对象的引用。
建议29:警惕默认参数潜在的问题,特别是当默认参数为可变对象时。
建议30:函数中慎用变长参数 args和 kargs。
(1)这种使用太灵活,从而使得函数签名不够清晰,可读性较差;
(2)如果因为函数参数过多而是用变长参数简化函数定义,那么一般该函数可以重构;
建议31:深入理解str()和repr()的区别。
(1)两者之间的目标不同:str主要面向客户,其目的是可读性,返回形式为用户友好性和可读性都比较高的字符串形式;而repr是面向Python解释器或者说Python开发人员,其目的是准确性,其返回值表示Python解释器内部的定义;
(2)在解释器中直接输入变量,默认调用repr函数,而print(var)默认调用str函数;
(3)repr函数的返回值一般可以用eval函数来还原对象;
(4)两者分别调用对象的内建函数 str ()和 repr ();
建议32:分清静态方法staticmethod和类方法classmethod的使用场景。
四、库的使用
建议33:掌握字符串的基本用法。
建议34:按需选择sort()和sorted()函数。
(1)sort()是列表在就地进行排序,所以不能排序元组等不可变类型;
(2)sorted()可以排序任意的可迭代类型,同时不改变原变量本身;
建议35:使用copy模块深拷贝对象,区分浅拷贝(shallow copy)和深拷贝(deep copy)。
建议36:使用Counter进行计数统计,Counter是字典类的子类,在collections模块中。
建议37:深入掌握ConfigParse。
建议38:使用argparse模块处理命令行参数。
建议39:使用pandas处理大型CSV文件。
(1)Python本身提供一个CSV文件处理模块,并提供reader、writer等函数;
(2)Pandas可提供分块、合并处理等,适用于数据量大的情况,且对二维数据操作更方便;
建议40:使用ElementTree解析XML。
建议41:理解模块pickle的优劣。
(1)优势:接口简单、各平台通用、支持的数据类型广泛、扩展性强;
(2)劣势:不保证数据操作的原子性、存在安全问题、不同语言之间不兼容;
建议42:序列化的另一个选择JSON模块:load和dump操作。
建议43:使用traceback获取栈信息。
建议44:使用logging记录日志信息。
建议45:使用threading模块编写多线程程序。
建议46:使用Queue模块使多线程编程更安全。
五、设计模式
建议47:利用模块实现单例模式。
建议48:用mixin模式让程序更加灵活。
建议49:用发布-订阅模式实现松耦合。
建议50:用状态模式美化代码。
六、内部机制
建议51:理解build-in对象。
建议52:init ()不是构造方法,理解 new ()与它之间的区别。
建议53:理解变量的查找机制,即作用域。
(1)局部作用域;
(2)全局作用域;
(3)嵌套作用域;
(4)内置作用域;
建议54:理解为什么需要self参数。
建议55:理解MRO(方法解析顺序)与多继承。
建议56:理解描述符机制。
建议57:区别 getattr ()与 getattribute ()方法之间的区别。
建议58:使用更安全的property。
建议59:掌握元类metaclass。
建议60:熟悉Python对象协议
建议61:利用操作符重载实现中缀语法。
建议62:熟悉Python的迭代器协议。
建议63:熟悉Python的生成器。
建议64:基于生成器的协程和greenlet,理解协程、多线程、多进程之间的区别。
建议65:理解GIL的局限性。
建议66:对象的管理和垃圾回收。
七、使用工具辅助项目开发
建议67:从PyPI安装第三方包。
建议68:使用pip和yolk安装、管理包。
建议69:做paster创建包。
建议70:理解单元测试的概念。
建议71:为包编写单元测试。
建议72:利用测试驱动开发(TDD)提高代码的可测性。
建议73:使用Pylint检查代码风格。
(1)代码风格审查;
(2)代码错误检查;
(3)发现重复以及不合理的代码,方便重构;
(4)高度的可配置化和可定制化;
(5)支持各种IDE和编辑器的集成;
(6)能够基于Python代码生成UML图;
(7)能够与Jenkins等持续集成工具相结合,支持自动代码审查;
建议74:进行高效的代码审查。
建议75:将包发布到PyPI。
八、性能剖析与优化
建议76:了解代码优化的基本原则。
建议77:借助性能优化工具。
建议78:利用cProfile定位性能瓶颈。
建议79:使用memory_profiler和objgraph剖析内存使用。
建议80:努力降低算法复杂度。
建议81:掌握循环优化的基本技巧。
(1)减少循环内部的计算;
(2)将显式循环改为隐式循环,当然这会牺牲代码的可读性;
(3)在循环中尽量引用局部变量;
(4)关注内层嵌套循环;
建议82:使用生成器提高效率。
建议83:使用不同的数据结构优化性能。
建议84:充分利用set的优势。
建议85:使用multiprocessing模块克服GIL缺陷。
建议86:使用线程池提高效率。
建议87:使用Cythonb编写扩展模块。