读书笔记 -- 《编写高质量代码:改善python程序的91个建议》


我总想睡一觉就能回到过去,可早上起来发现,还是没有任何变化。 -- 《素媛》


第二章 编程惯用法

利用 assert 发现问题

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

相当于

x, y = 1, 2
if __debug__ and not x == y:
    raise AssertionError("not equal")
Notes:
  • 断言会影响性能

使用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)

利用 lazy evaluation 特性

对于 Python 中的条件表达式 if x and y ,在 x 为 false 的情况下 y 表达式的值将不再计算。而对于 if x or y ,当 x 的值为 true 的 时候将直接返回,不再计算 y 的值。

from time import time

t = time()
abbreviations = ["cf.", "e.g.", "ex.", "etc.", "flg."]

for i in xrange(100000):
    for w in ("Mr.", "Hat", "is", "chasing", "."):
        if w in abbreviations and w[-1]=='.': # 这句性能较差
            # if w[-1] == '.' and w in abbreviations: # 性能好
            pass

print time() - t

enumerate(sequence, start=0)

它具有一定的惰性(lazy),每次仅在需要的时候才会产生一个(index, item)对

for index, ele in enumerate(range(4)):
    print(index, ele)

第三章 基础语法

with 语句

with 语句可以在代码块执行完毕后还原进入该代码块时的现场,执行步骤如下:

  • 计算表达式的值,返回一个上下文管理器对象
  • 加载上下文管理器对象的 exit() 方法以备后用
  • 调用上下文管理器对象的 enter() 方法
  • 如果 with 语句中设置了目标对象,则将 enter() 方法的返回值赋值给目标对象
  • 执行 with 中的代码块
  • 如果步骤 5 中代码正常结束,调用上下文管理其对象的 exit() 方法,其返回值直接忽略
  • 如果步骤 5 中代码执行过程中发生异常,调用上下文管理器对象的 exit() 方法,并将异常类型、值及 traceback 信息作为参数传递给 exit() 方法。如果__exit__() 返回值为 false,则异常会被重新抛出;如果其返回值为 true,异常被挂起,程序继续执行。

else 语句简化循环

在循环中, else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断。当循环“自然”终结(循环条件为假)时 else 从句会被执行一次,而当循环是由 break 语句中断时, else 子句就不被执行。

def print_prime2(n):
    for i in xrange(2, n):
        for j in xrange(2, i):
            if i % j == 0:
            break
        else:
            print("{} is a prime number".format(i))

连接字符串使用 join 而非 +

(" ").join(["i","am", "pinsily"])

函数传参

函数传参既不是传值也不是传引用,而是传对象,所以对可变对象的修改是共享的,而对不可变对象的修改一般是生成一个新对象赋值而成

str() 和 repr() 的区别

str()repr() 都可以将 Python 中的对象转换为字符串。但是: - str() 面向用户,repr() 面向解释器或开发人员,debug 时使用 - 解释器的输出默认调用 repr(),而 print 调用 str()


第四章 库

sort() 和 sorted()

  • sort() 直接修改原列表,返回 None,sorted() 则返回修改后的序列
  • 字典排序
>>> phone_book = {"Linda": "7750", "Bob": "9345", "Carol": "5834"}
>>> from operator import itemgetter
>>> sorted_pb = sorted(phone_book.items(), key=itemgetter(1))
>>> print(sorted_pb)
[('Carol', '5834'), ('Linda', '7750'), ('Bob', '9345')]

使用 Counter 进行计数统计

from collections import Counter
some_data = ["a", "2", 2, 4, 5, "2", "b", 4, 7, "a", 5, "d", "z", "a"]
print(Counter(some_data))

第六章 内部机制

__init__() 不是构造方法

__init__() 并不是真正意义上的构造方法, __init__() 方法所做的工作是在类的对象创建好之后进行变量的初始化。 __new__() 方法才会真正创建实例,是类的构造方法。

名字查找机制

在 Python 中,所有所谓的变量,其实都是名字,这些名字指向一个或多个 Python 对象。

所有的这些名字,都存在于一个表里(又称为命名空间),一般情况下,我们称之为局部变量(locals),可以通过 locals() 函数调用看到。

在一个 globals() 的表里可以看到全局变量,注意如果是在 Python shell中执行locals(),也可以看到全局的变量。如果在一个函数里面定义这些变量,情况就会有所不同。

Python 中所有的变量名都是在赋值的时候生成的,而对任何变量名的创建、查找或者改变都会在命名空间(namespace)中进行。变量名所在的命名空间直接决定了其能访问到的范围,即变量的作用域。

迭代器协议

其实 for 语句就是对获取容器的迭代器、调用迭代器的 next() 方法以及对 StopIteration 进行处理等流程进行封装的语法糖(类似的语法糖还有 in/not in 语句)。

  • product() :计算 m 个序列的 n 次笛卡尔积
  • permutations() :产生全排列
  • combinations() :产生无重复元素的组合
  • combinations_with_replacement() :产生有重复元素的组合

GIL 的局限性

GIL 被称为全局解释器锁(Global Interpreter Lock),是 Python 虚拟机上用作互斥线程的一种机制,它的作用是保证任何情况下虚拟机中只会有一个线程被运行,而其他线程都处于等待 GIL 锁被释放的状态。不管是在单核系统还是多核系统中,始终只有一个获得了 GIL 锁的线程在运行,每次遇到 I/O 操作便会进行 GIL 锁的释放。

单核 CPU 中,GIL 对多线程的执行并没有太大影响,因为单核上的多线程本质上就是顺序执行的。但对于多核CPU,多线程并不能真正发挥优势带来效率上明显的提升,甚至在频繁I/O 操作的情况下由于存在需要多次释放和申请 GIL的情形,效率反而会下降。

对象的管理与垃圾回收

Python 中内存管理的方式:Python 使用引用计数器(Reference counting)的方法来管理内存中的对象,即针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。当其他对象引用该对象时,其引用计数会增加 1,而删除一个队当前对象的引用,其引用计数会减1。只有当引用计数的值为 0时的时候该对象才会被垃圾收集器回收,因为它表示这个对象不再被其他对象引用,是个不可达对象。引用计数算法最明显的缺点是无法解决循环引用的问题,即两个对象相互引用。

循环引用常常会在列表、元组、字典、实例以及函数使用时出现。对于由循环引用而导致的内存泄漏的情况,可以使用 Python 自带的一个 gc 模块,它可以用来跟踪对象的“入引用(incoming reference)“和”出引用(outgoing reference)”,并找出复杂数据结构之间的循环引用,同时回收内存垃圾。有两种方式可以触发垃圾回收:一种是通过显式地调用 gc.collect() 进行垃圾回收;还有一种是在创建新的对象为其分配内存的时候,检查threshold阈值,当对象的数量超过threshold 的时候便自动进行垃圾回收。默认情况下阈值设为(700,10,10),并且 gc 的自动回收功能是开启的,这些可以通过 gc.isenabled()查看。