2.《Python高质量代码的91个建议》编程惯用法

1 建议 8:利用 assert 语句来发现问题

断言(assert)基本语法:assert expression1, ["," expression2]

x, y = 1, 2
assert x == y, "not equal"

适合场景:

  • 正常情况下总是为真的场合
  • 需要确认函数返回值是否合理
  • 会影响到后续逻辑的先决条件

注意事项:

  • 断言对性能有一定的影响
  • 不要使用断言来检查用户的输入
  • 优先使用Python自带的异常处理功能
  • 运行脚本的时候加上参数 -O ,可忽略与断言相关的语句

2 建议 9:数据交换值的时候不推荐使用中间变量

x, y = y, x

Python的字节码分析

3 建议 10:充分利用 Lazy evaluation 的特性

真正需要执行的时候才计算表达式的值

  • 避免不必要的计算,带来性能上的提升。例如if x and y,在 x 为 false 的情况下 y 表达式的值将不再计算
  • 节省空间,使得无限循环的数据结构成为可能。例如斐波那契的生成器

itertools islice 可以对迭代器做切片

4 建议 11:理解枚举替代实现的缺陷

枚举适用于值是固定且有限的变量,比如描述星期、月份等待。

现在基本不会考虑枚举替代的事情了,所有本小节内容暂略

但补充一些目前枚举的两种常用方式

5 建议 12:不推荐使用 type 来进行类型检查

  • Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换
  • 基于内建类型扩展的用户自定义类型,type 函数并不能准确返回结果
  • 在古典类中,所有类的实例的 type 值都相等
  • 可以使用 isinstance() 函数来检测类型
a = 2
isinstance (a,int) #True
isinstance (a,str) #False
isinstance (a,(str,int,list)) #True

6 建议 13:尽量转换为浮点类型后再做除法

这个问题在 Python3 中已经不存在了

<code>decimal</code> 提供精准的浮点运算

7 建议 14:警惕 eval() 的安全漏洞

  • 如果使用对象不是信任源,应该避免使用 eval
  • 在需要使用 eval 的地方可用安全性更好的 ast.literal_eval 替代

8 建议 15:使用 enumerate() 获取序列迭代的索引和值

seq = ['one', 'two', 'three']
for i, element in enumerate(seq):
    print(i, element)
#output
0 one 
1 two 
2 three

9 建议 16:分清 == 与 is 的适用场景

  • is 是比较两个对象在内存中是否拥有同一块内存空间
  • == 是用来检验两个对象的值是否相等的

前者是相等性比较,后者是一致性校验

10 建议 17:考虑兼容性,尽可能使用 Unicode

locale.getdefaultlocale()返回的是系统默认编码:Linux中是utf-8,Windows中是gbk sys.getdefaultencoding()返回的是Python默认编码:Python3是utf-8,Python2是ascii

  • 读取文件的默认编码格式都由当下的读取环境/编辑器决定
  • Python的 open() 函数使用的是操作系统的默认编码

字符编码基础补充

11 建议 18:构建合理的包层次来管理 module

  • 本质上每一个 Python 文件都是一个模块
  • 而包的作用是合理组织代码、避免命名冲突
  • 包通过文件__init__.py和普通目录区分
  • __init__.py文件中定义__all__变量,可以控制需要导入的子包或者模块

12 细节补充:

12.1 Python的字节码分析

Python 的字节码是一种类似汇编指令的中间语言,但是一个字节码指令并不是对应一个机器指令。通过 dis 模块可以进行分析:

import dis
def swap1():
    x, y = 2, 3
    x, y = y, x

结果如下:

def swap2():
    x, y = 2, 3
    temp = x
    x = y
    y = temp

dis.dis(swap1)
dis.dis(swap2)

结果如下:

结果分析:

通过字节码可以看出,swpa1 对应的字节码中有 2 个 LOAD_FAST 指令、2 个 STORE_FAST 指令和 1 个 ROT_TWO 指令,而 swap2 函数对应的共生成了 3 个 LOAD_FAST 指令和 3 个 STORE_FAST 指令。而指令 ROT_TWO 的主要作用是交换两个栈的最顶层元素,它比执行一个 LOAD_FAST + STORE_FAST 指令更快。

12.2 itertools.islice()可以对迭代器做切片

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
from itertools import islice
print list(islice(fib(), 5))

12.3 枚举的两种常用方式

  • 直接使用Enum列出多个枚举值来创建枚举类
import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'AUTUMN', 'WINTER'))
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)

# 根据枚举变量名访问枚举对象
print(Season['WINTER']) # Season.WINTER
# 根据枚举值访问枚举对象
print(Season(2)) # Season.SUMMER

# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)
#output
Season.SPRING 
SPRING 
1 
Season.WINTER 
Season.SUMMER 
SPRING => Season.SPRING , 1 
SUMMER => Season.SUMMER , 2 
AUTUMN => Season.AUTUMN , 3 
WINTER => Season.WINTER , 4
  • 通过继承Enum基类来派生枚举类
import enum
class Sex(enum.Enum):
    MALE = '男', '纯爷们'
    FEMALE = '女', '纯姐们'
    def __init__(self, cn_name, desc):
        self._cn_name = cn_name
        self._desc = desc
    @property
    def desc(self):
        return self._desc
    @property
    def cn_name(self):
        return self._cn_name
# 访问FEMALE的name
print('FEMALE的name:', Sex.FEMALE.name)
# 访问FEMALE的value
print('FEMALE的value:', Sex.FEMALE.value)
# 访问自定义的cn_name属性
print('FEMALE的cn_name:', Sex.FEMALE.cn_name)
# 访问自定义的desc属性
print('FEMALE的desc:', Sex.FEMALE.desc)
#output
FEMALE的name: FEMALE 
FEMALE的value: ('女', '纯姐们') 
FEMALE的cn_name: 女 
FEMALE的desc: 纯姐们

参考链接

12.4 字符编码基础补充

字符编码基础

#枚举 #字节码

往年同期文章