【笔记】《编写高质量Python代码的59个有效方法》

文章目录

第一章 用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

后续内容个人用到的不多,留个目录需要再补


第三章 类与继承

22. 尽量用辅助类来维护程序的状态,而不要用字典和元组

23. 简单的接口应该接受函数,而不是类的实例

24. 以@classmethod形式的多态去通用地构建对象

25. 用super初始化父类

26. 只在使用Mix-in组件制作工具类时进行多重继承

27. 多用public属性,少用private属性

28. 继承collections.abc以实现自定义的容器类型

第四章 元类和属性

29. 用纯属性取代get和set方法

30. 考虑用@property来代替属性重构

31. 用描述符来改写需要复用的@property方法

32. 用__getattr__、__getattribute__、__setattr__实现按需生成的属性

33. 用元类来验证子类

34. 用元类来注册子类

35. 用元类来注解类的属性

第五章 并发及并行

36. 用subprocess模块来管理子进程

37. 可以用线程来执行阻塞式I/O,但不要用它做平行计算

38. 在线程中使用Lock来防止数据竞争

39. 用Queue来协调各线程之间的工作

40. 考虑用协程来并发地运行多个函数

41. 考虑用concurrent.futures来实现真正的平行计算

第六章 内置模块

42. 用functools.wraps来定义函数修饰器

43. 考虑以contextlib和with语句来改写可复用的try/finally代码

44. 用copyreg实现可靠的pickle操作

45. 应该用datetime模块来处理本地时间,而不是time模块

46. 使用内置算法与数据结构

47. 在重视精度的场合,应该使用decimal

48. 学会安装由Python开发者社区所构建的模块

第七章 协作开发

49. 为每个函数、类和模块编写文档字符串

50. 用包来安排模块,并提供稳固的API

51. 为自编的模块定义根异常,以便将调用者与API相隔离

52. 用适当的方式打破循环依赖关系

53. 用虚拟环境隔离项目,并重建其依赖关系

第八章 部署

54. 考虑用模块级别的代码来配置不同的部署环境

55. 通过repr字符串来输出调试信息

56. 用unittest来测试全部代码

57. 考虑用pdb实现交互调试

58. 先分析性能,然后再优化

59. 用tracemalloc来掌握内存的使用及泄露情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值