Note of 《Effective Python》(第一章 - 第三章)

记得以前上大学的时候, 去图书馆借了一本《代码简洁之道》. 虽然大部分的内容都忘得差不多了, 但里边的一些思想至今还是收益颇深.
最近开始看一本书叫做《Effective Python: 59 Specific Ways to Write Better Python》, 把里边一些印象深刻的东西记录在这篇日志里.

###Chapter 1: Pythonic Thinking:

  1. Pythonic Thinking: import this
  2. 讲到选择 Python2 还是 Python3 的时候, 作者还是推荐大家尽量选择 Python3.
    个人觉得一个初学者总是问学 Python2 和 Python3 的问题真的是很蠢的一种行为, 我以后还是要慢慢去学会写兼容 Python 2 和 3 代码.
  3. PEP 8 Style: 如果用 Pycharm 的话, 风格的不合适也是会用 warning 提示你的, 所以这个不必担心.
    但个人认为提高 Python 代码可读性, 还是要代码写的很有节奏, 配上简洁明了的注释.
    例子: if len(l) == 0: --> if not l:
  4. standard library modules > third-party modules > own modules.
    import 包的顺序, 以前写 java 的时候也是这么推荐的.
  5. 编码的问题 (two types of representing se):
    Python3: bytes(raw 8-bit values) and str(Unicode characters)
    Python2: str(raw 8-bit values) and unicode
    编码的问题原理我还是不是特别清楚, 以前上课的时候提到过 Unicode 编码的思想, 有空还是要深入了解一下.
    Things to remember in this chapter:
    • In python 3, bytes contains sequences of 8-bit values, str caontains sequences of Unicode characters. bytes and str instances cann’t be used with operateors(like > or +)
    • In Python 2, str contains sequences of 8-bit values, unicode contains sequences of Unicode characters. str and unicode can be used together with operators if the str only contains 7-bit ASCII characters.
    • Use helper functions to ensure that the inputs you operate on are the type of character sequence you expect (8-bit values, UTF-8 encoded characters, Unicode characters, etc.).
    • If you want to read or write binary data to/from a file, always open the file using a binary mode (like 'rb' or 'wb')
  6. 作者推荐这类只用一行简洁的写法: red = my_values.get(‘red’, [”])[0] or 0
    不由的让人想起了 Perl 的优美使用: open BOOK, "hp1.txt" or die "$0: open '$file' failed: $!"
  7. 数组的 slice: 最经典还是这个反转数组 (字符串) 操作: somelist[::-1]
  8. 要尽量用 List Comprehension(squares = [x**2 for x in l if x % 2 == 0, 而且更强大) 而不是 map 或者 filte.
    字典和 set 也支持 Comprehension Expression: rank_dict = {rank: name for name, rank in chile_ranks.items()}
  9. Consider generator expressions for large comprehensions(防止消耗过大的空间):
    Bad: value = [len(x) for x in open(‘/tmp/my_file.txt’)]
    Good: (len(x) for x in open(‘/tmp/my_file.txt’))
    看了这一小章终于明白 generator 存在的意义了.
    generator 唯一的缺点就是它只能用一次.
    stackoverflow 上的一个不错的回答: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do
  10. zip: 唯一要关注的问题就是在 Python2 中, zip 并不是一个 generator, 所以它可能会占用很大的内存, 所以要用 izip. 而且当 zip 的两个参数的长度不一样的时候, 一个参数长出来的部分就会消失咯.
  11. for循环后边要尽量不用else
  12. try/except/else/finally 的逻辑(异常 Python 用的不多, 但和以前 Java 的结构好像并没有什么不同). 例如没有 exception 的话就会走 else.

###Chapter 2: Functions:

  1. 要使用 exception 而不是返回None (“return” or “return None”), 因为 None, 0, [], ‘’ 都会被当成 False.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def divide(a, b):
    try:
    return a / b
    except ZeroDivisionError as e:
    raise ValueError('Invalid b') from e

    if __name__ == '__main__':
    try:
    result = divide(1, 0)
    except ValueError:
    print('invalid input')
    else:
    print('result is %.1f' % result)

2. 用一个 helper 方法去自定义排序.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def sort_priority(values, group):
def helper(x):
if x in group:
return 0, x
return 1, x

values.sort(key=helper)

l = [1, 2, 3, 4, 5, 6]
g = {4, 5}

sort_priority(l, g)
print(l)
```
能这么做原因: 1.Python 支持闭包 (closures). 2. 方法可以直接当做变量使用. 3.Python 有自己的排序规则来自定义 key.
这一小节还讨论了一些 scope 的问题, 比如在 helper 里去改变 sort_priority 的变量肯定就没有效果, 要加上 `nonlocal`, 不禁让人想起了 `global`, 他们的作用是刚好相补的.
但是作者还是推荐说 `nonlocal` 有风险, 要谨慎使用. 可以用一个类实现一样的功能或者使用 list.
为什么 list 可以无视 scope 呢, 还是不太明白?
"The trick is that the value for found is a list, which is `mutable`."
_
查了一下, 意思就是比如 `x=y=1`, 改变 immutable 变量的值的话是会重新创建一个变量.
如果是 `x=y=[]`, 改变 mutable 的变量的话, x 和 y 的值就一起变了. 可以理解为 C 语言中传的地址. y is a copy of x's reference.
Stackoverflow 上的详细解释: [http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference](http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference)
_
可以这么写不去改变传入的参数: `list(a)` or `a[:]` 来创建新对象.
3.Generator, 防止内存爆掉.
```python
def index_word_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter ==' ':
yield index + 1

上边说到过, 一个 iterator 只能用一次, 比如这个例子:

1
2
print(list(it))
print(list(it)) # Already exhausted <-- XD

而且你去使用一个被使用过的 iterator 并不会报错, 所以要格外的小心.
可以 numbers = list(it) 提前 copy 一份, 但我总觉得怪怪的.
作者最后说要用 iter 方法, 但不是很懂:
Iterator protocol 大意就是每次使用这个 iterator 的时候, iter都会创建一个新的 iterator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def normalize(numbers):
total = sum(numbers)
result = []

for v in numbers:
result.append(v/total)

return result


class ReadVistors(object):
def __iter__(self):
for i in range(1, 5):
yield i

def readVistors():
for i in range(1, 5):
yield i

v = ReadVistors()
# v = readVistors() # null
print(normalize(v))

可以用这个方法检测 iterator 是否可以多次使用:

1
2
if iter(numbers) is iter(numbers):
raise TypeError('must suplly a container')

4.variable positional ariguments: 如果传入的参数是个未知长度数组的话, 可以这么写:

1
2
3
4
5
def log(message, *values):
...

log('My numbers are', 1, 2)
log('Hi there')

但是这个巧妙的用法也有一些问题:

  • 的话会在传入参数前, 把 values 变成一个 tuple. 所以要是传入的是一个 generator 的话, 内存就很有可能爆掉.
  • 在方法中新增一个参数的话, 很可能造成一些很难发现的 bugs.
    5. 在使用 Python 的默认参数是 mutable({}, [])的时候, 要格外小心, 因为这个 default argument 只有在初始化的时候被 evaluated 一次.
    最好的解决方法是用 None:
    1
    2
    3
    def log(message, when=None):
    when = datetime.now() is when is None else when
    ...

###Chapter 3: Classes and Inheritance:

  • 1) 要避免字典里套字典, 可以用多个类来替换.
    2) 在考虑用 class 前, 可以尝试 namedtuple 作为一种轻量级的不可修改的 data container.
  • Python 中的方法也是和变量一样是对象, 所以可以传来传去~ 比如 sort 的 key 参数就是接收一个排序的方法
  • Python 允许类中定义一个 __call__ 方法,

    1
    2
    counter = BetterCountMissing()
    result = defaultdict(counter, current) # Relies on __call__
  • @classmethod 和 @statisticmethod:
    书中解释了一大堆, 不是特别懂, 大意就是要把相关的方法写到类里边. 摘 Stack Overflow 上的两个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @classmethod
    def from_string(cls, date_as_string):
    day, month, year = map(int, date_as_string.split('-'))
    date1 = cls(day, month, year)
    return date1

    date2 = Date.from_string('11-09-2012')


    @staticmethod
    def is_date_valid(date_as_string):
    day, month, year = map(int, date_as_string.split('-'))
    return day <= 31 and month <= 12 and year <= 3999

    # usage:
    is_date = Date.is_date_valid('11-09-2012')
  • Initialize Parent Classes with super(看了半天头都晕了)

  • Item26: 跳过.
  • Item28: collections.abc

###Chapter 4: Metaclasses and Attributes:

  • Item 29:

< 未完待续 >