文章目录
第一章 用Pythonic方式来思考
1. 确认Python版本
import sys
print(sys.version_info,'\n')
print(sys.version)
sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
3.6.8 |Anaconda custom (64-bit)| (default, Dec 29 2018, 19:04:46)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
2. 遵循PEP8风格指南
https://www.python.org/dev/peps/pep-0008/
- 空白
- 用space来缩进,不要用tab
- 每一层缩进4个空格
- 每行字符不超过79
- 多行表达式,除首行外,各行应多缩进4个空格
- 文件中函数和类之间用两个空行隔开
- 同一个类中,方法之间用一个空行隔开
- 使用下标获取元素时,不要留空格
- 变量赋值时,等号左右各一个空格
- 命名
- 函数、变量、属性,用小写字母+下划线【lowercase_underscore】
- 受保护实例属性,单下划线开头【_leading_underscore】
- 私有实例属性,双下划线开头【__double_leading_underscore】
- 类、异常,首字母大写【CapitalizedWord】
- 模块级别敞亮,全大写+下划线【ALL_CAPS】
- 类实例方法首参为self,表示对象自身;类方法首参为cls,表示类自身
- 表达式和语句
- 内联否定时,否定放在后面【if a is not b】
- 检测list是否为空时,不要用len(list),用【if not list】和【if list】
- 不要编写单行的if、for、while、except
- import语句方在文件开头
- 引用包时使用绝对名称,如【from bar import foo】,不要用【import foo】
- import语句按照标准库模块、第三方模块、自用模块排序,各模块内按首字母顺序排序
- pylint可以检查代码是否符合PEP8风格
3. 了解bytes、str、unicode的区别
- Python3种,bytes是8位值序列;str是Unicode字符序列
- decode将二进制数据转Unicode字符;encode将Unicode字符转为二进制数据
- 编码解码操作应放在外围,程序核心部分用Unicode字符(Python3的str、Python2的unicode)
- Python3处理二进制文件时,应以’wb’和’rb’来开启
#Python3中得到bytes和str
def to_str(bytes_or_str):
if isinstance(bytes_or_str,bytes):
value=bytes_or_str.decode('utf-8')
else:
value=bytes_or_str
return value
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str,str):
value=bytes_or_str.encode('utf-8')
else:
value=bytes_or_str
return value
print('to_str\t\t',to_str('哆啦A梦'))
print('to_bytes\t',to_bytes('哆啦A梦'))
print('bytes_to_str\t',to_str(to_bytes('哆啦A梦')))
to_str 哆啦A梦
to_bytes b'\xe5\x93\x86\xe5\x95\xa6A\xe6\xa2\xa6'
bytes_to_str 哆啦A梦
4. 用辅助函数取代复杂表达式
- 重复使用较少时使用【a if flag else b】三元操作符可以,重复多时应用辅助函数
5. 了解切割序列的方法
-
切片(slice)基本写法为list[start?stride],表示从start开始,每stride一个,直到end(不包括end)
-
从开头开始,或直到结尾,则不要写0,留空即可
-
切割列表时,数组不会越界
A=[1,2,3,4]
A[:100]
[1, 2, 3, 4]
- 切割会产生新列表,单一个:的切割即复制全列表;而直接对切割赋值会替换列表
A=[1,2,3,4,5]
B=A[:3]
B[1]='change'
print('A',A)
print('B',B)
A[2:4]=['cc']
print('A 赋值',A)
A [1, 2, 3, 4, 5]
B [1, 'change', 3]
A 赋值 [1, 2, 'cc', 5]
6. 单次切片操作内,不要同时指定start、end、stride
- 同时包括三者的的切片操作难以理解,可以采用步进切片;确实需要,考虑itertools模块的islice
7. 用列表推导(list comprehension)来取代map和filter
- 列表、字典、集合都支持,且可以用if过滤
A=[1,2,3,4,5,6]
print('map filter',list(map(lambda x:x**2,filter(lambda x:x%2==0,A))))
print('列表生成式',[x**2 for x in A if x%2==0])
map filter [4, 16, 36]
列表生成式 [4, 16, 36]
8. 不要用含有两个以上表达式的列表推导
- 列表推导可以多重循环,但多于两个的时候可读性差
matrix=[[1,2,3],[4,5,6],[7,8,9]]
print([x for row in matrix for x in row])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
9. 用生成器表达式来改写数据量较大的列表推导
- 列表推导会消耗大量内存;可以使用生成器来处理,迭代读取而不复制,省内存且速度快
- 用圆括号的到的即是生成器
(x for x in range(10))
<generator object <genexpr> at 0x11265e360>
10. 用enumerate代替range
A=['苹果','香蕉','西瓜']
for i in range(len(A)):
print(i,A[i])
for i,fruite in enumerate(A):
print(i,fruite)
0 苹果
1 香蕉
2 西瓜
0 苹果
1 香蕉
2 西瓜
11. 用zip函数同时遍历两个迭代器
- 对于两个有关联的列表,可以用zip拼接来共同遍历
- Python3的zip相当于生成器,如果两个列表不等,提前终止
A=['苹果','香蕉','西瓜']
B=['pingguo','xiangjiao','xigua']
for a,b in zip(A,B):
print(a,b)
苹果 pingguo
香蕉 xiangjiao
西瓜 xigua
12. 不要在for和while后面写else
- Python的特殊语法,当循环成功执行结束后执行else,不建议使用
13. 合理利用try/except/else/finally结构的每个代码块
- finally一定会执行,不管try中是否成果运行,如close文件句柄
- try/except/else结构,try执行失败则执行except,try执行成功则执行else
第二章 函数
14. 尽量用异常表示特殊情况,而不是None
15. 了解如何在闭包里使用外围作用域的变量
- 定义在某个作用域内的闭包来说,可以引用该作用域内的变量
- 比包内赋值作用域的同名变量不改变作用域变量的值,可以使用nonlocal来修改外围作用于值,尽量不要使用
#修改排序优先级
def sort_priority(values,group):
#闭包
def helper(x):
if x in group:
return (0,x)
else:
return (1,x)
values.sort(key=helper)
numbers=[8,3,1,2,5,4,7,6]
group={2,3,5,7}
sort_priority(numbers,group)
print(numbers)
[2, 3, 5, 7, 1, 4, 6, 8]
16. 考虑用生成器来改写直接返回列表的函数
- 该方法节省内存;相比造list并不断append更清晰
#返回字符串间断点
def index_words_iter(text):
if text:
yield 0
for index,letter in enumerate(text):
if letter==' ':
yield index+1
print('iter index of string',list(index_words_iter('12 456 89')))
iter index of string [0, 3, 7]
17. 在参数上面迭代时,要多加小心
- 迭代器遍历完成后,即没有内容,重新循环迭代不会返回值(认为迭代器已经空了)
- 解决方法一,使用固定的列表来迭代
- 解决方法二,每次循环重新生成一个迭代器使用
- 解决方法三,定义一个可迭代的容器类,修改__iter__方法实现为生成器
18. 用数量可变的位置参数减少视觉杂讯
- 在def中使用*args,可以令函数接受数量可变的位置参数
- 调用函数时,可以使用*操作符,使序列元素作为位置参数传给函数
- 该操作首先将输入参数转为元组,若对生成器使用*操作符,则可能存在内存风险
- 在已使用*args参数的函数上继续添加参数,可能难以排查bug
def log(message,*values):
if not values:
print(message)
else:
print('|'.join(str(x) for x in values))
log('英文字母','A','B','C','D')
log('没有字母')
A|B|C|D
没有字母
19. 用关键字参数表达可选的行为
- 即使用关键字参数调用函数,如function(para1=xxx,parab=yyy)
- 定义函数时,可以将需要传入但通常使用某一值的参数设置默认值,则调用时不指定数值即使用默认值
- 如此使用函数,后续修改挺假函数参数定义时,不会出错
20. 用None和文档字符串来描述具有动态默认值的参数
- 具有默认值的函数,其默认赋值只执行一次,因此对于列表、字典等可变变量,多次调用并不会多次默认赋值
def append_list(string,lst=[]):
lst.append(string)
return lst
print('期望[a]',append_list('a'))
print('期望[b]',append_list('b'))
期望[a] ['a']
期望[b] ['a', 'b']
def append_list(string,lst=None):
if not lst:
lst=[]
lst.append(string)
return lst
print('期望[a]',append_list('a'))
print('期望[b]',append_list('b'))
期望[a] ['a']
期望[b] ['b']
21. 用只能以关键字形式指定的参数来确保代码明晰
- 定义函数时,在参数定义中,加*号,其前为位置参数,其后为必须指定关键字的参数
def operate(a,b,is_minus=False):
return a-b if is_minus else a+b
print('plus',operate(1,2))
print('minus',operate(1,2,True))
plus 3
minus -1
def operate(a,b,*,is_minus=False):
return a-b if is_minus else a+b
print('minus',operate(1,2,is_minus=True))
print('minus',operate(1,2,True))#wrong
minus -1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-16-86fb7a0f3f98> in <module>()
2 return a-b if is_minus else a+b
3 print('minus',operate(1,2,is_minus=True))
----> 4 print('minus',operate(1,2,True))#wrong
TypeError: operate() takes 2 positional arguments but 3 were given