1.准备

1.说明:《Mastering Python》读书笔记

2.要求:首先,希望你是处在python3版本,然后拥有一个干净的虚拟环境更是必要。

3.virtualenv或者anaconda都是不错的选择

2.Pythonic Syntax, Common Pitfalls, and Style Guide

2.1.Pythonic code

对于python开发者,无不希望写出pythonic风格的代码,请谨记以下几点:

- Clean
- Simple
- Beautiful
- Explicit
- Readable

具体的话请打开你的python解释器:

import this

细细感受吧。

2.1.2.Differences between value and identity comparisons

对于下面这个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
a1 = 200 + 56
a2 = 256
b1 = -4 - 1
b2 = -5
c1 = 200 + 57
c2 = 257
d1 = -4 - 2
d2 = -6
print('%r == %r: %r' % (a1, a2, a1 == a2))
print('%r is %r: %r' % (a1, a2, a1 is a2))
print('%r == %r: %r' % (b1, b2, b1 == b2))
print('%r is %r: %r' % (b1, b2, b1 is b2))
print('%r == %r: %r' % (c1, c2, c1 == c2))
print('%r is %r: %r' % (c1, c2, c1 is c2))
print('%r == %r: %r' % (d1, d2, d1 == d2))
print('%r is %r: %r' % (d1, d2, d1 is d2))
256 == 256: True
256 is 256: True
-5 == -5: True
-5 is -5: True
257 == 257: True
257 is 257: False
-6 == -6: True
-6 is -6: False

可以看到,在值相同的情况下,使用==比较都返回True自不必说,为何有的id相同有的id却不同?

这是因为,在pyhton中,会提前为一小部分整数提前分配空间,这个整数的范围是[-5,256],当整数范围大于256或者小于-5时,python便会为该整数创建对象,于是造成id不同。如果想深入了解,可阅读深入 Python 整数对象的实现

2.1.3.Loops

对于一个列表,比如:

1
my_list = [1, 2, 3, 4]

想将其输出为[index:value]这种列表格式,请看以下几种实现方法:

1
2
3
4
5
6
7
8
index = 0
my_list = [1, 2, 3, 4]
new_list = []
for value in my_list:
    value = "{0}:{1}".format(index, value)
    new_list.append(value)
    index += 1
print(new_list)

不得不说,实在很繁琐,我们可以利用python提供的enumerate类:

1
2
3
4
5
new_list = []
for index, value in enumerate(my_list):
    value = "{0}:{1}".format(index, value)
    new_list.append(value)
print(new_list)

第二种方法确实精简了不少,但是,优雅的pythonic不是浪得虚名,请看这种:

1
2
my_list = [1, 2, 3, 4]
["{0}:{1}".format(index, value) for index, value in enumerate(my_list)]
['0:1', '1:2', '2:3', '3:4']

2.2.Common pitfalls

虽说python自诩是一种清晰易读无歧义的优雅语言,但是这个伟大的目标并不是那么容易实现。

2.2.1.Function arguments

需要知道的是,千万不要使用可变参数作为函数默认参数,看下面这样一个例子:

1
2
3
4
5
6
7
8
9
def spam(key, value, list_=[], dict_={}):
    list_.append(value)
    dict_[key] = value
    print('List: %r' % list_)
    print('Dict: %r' % dict_)


spam('key 1', 'value 1')
spam('key 2', 'value 2')
List: ['value 1']
Dict: {'key 1': 'value 1'}
List: ['value 1', 'value 2']
Dict: {'key 2': 'value 2', 'key 1': 'value 1'}

按照代码的思路,输出的结果应该是下面这样才对:

1
2
3
4
List: ['value 1']
Dict: {'key 1': 'value 1'}
List: ['value 2']
Dict: {'key 2': 'value 2'}

实际上并非如此,首先,list以及dict都是可变对象,当spam函数被定义,list_dict_就会被创建(但是它们只被创建一次),随后调用函数的时候,list_dict_并没有被重新创建,于是,这个陷阱便出现了。

正确的做法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def spam(key, value, list_=None, dict_=None):
    if list_ is None:
        list_ = []
    if dict_ is None:
        dict_ = {}
    list_.append(value)
    dict_[key] = value
    print('List: %r' % list_)
    print('Dict: %r' % dict_)


spam('key 1', 'value 1')
spam('key 2', 'value 2')
List: ['value 1']
Dict: {'key 1': 'value 1'}
List: ['value 2']
Dict: {'key 2': 'value 2'}

2.2.2.Class properties

在类的定义中,也存在这样的问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Spam(object):

    list_ = []
    dict_ = {}

    def __init__(self, key, value):
        self.list_.append(value)
        self.dict_[key] = value
        print('List: %r' % self.list_)
        print('Dict: %r' % self.dict_)


Spam('key 1', 'value 1')
Spam('key 2', 'value 2')

output:

List: ['value 1']
Dict: {'key 1': 'value 1'}
List: ['value 1', 'value 2']
Dict: {'key 1': 'value 1', 'key 2': 'value 2'}

替代方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Spam(object):
    def __init__(self, key, value):
        self.list_ = [key]
        self.dict_ = {key: value}
        print('List: %r' % self.list_)
        print('Dict: %r' % self.dict_)


Spam('key 1', 'value 1')
Spam('key 2', 'value 2')
List: ['key 1']
Dict: {'key 1': 'value 1'}
List: ['key 2']
Dict: {'key 2': 'value 2'}
<__main__.Spam at 0x10498a2b0>

2.2.3.Modifying variables in the global scope

在函数内对变量进行操作也有需要注意的地方。

1
2
3
4
5
6
7
8
9
spam = 1


def eggs():
    spam += 1
    print(spam)


eggs()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-8-52c0d52ad689> in <module>()
      7 
      8 
----> 9 eggs()
<ipython-input-8-52c0d52ad689> in eggs()
      3 
      4 def eggs():
----> 5     spam += 1
      6     print(spam)
      7 
UnboundLocalError: local variable 'spam' referenced before assignment

eggs函数内,如果某个变量有被赋值,那么该变量就是局部变量,如果没有赋值,那么便会被解释为全局变量,所以对局部变量spam进行操作的时候,在函数内并没有进行赋值,spam被解释成全局变量,自然会出现错误。

2.2.4.Modifying while iterating

当对某个字典进行迭代的时候:

1
2
3
4
dict_ = {'spam': 'eggs'}

for key in dict_:
    del dict_[key]
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-9-b88de85f5f0b> in <module>()
      1 dict_ = {'spam': 'eggs'}
      2 
----> 3 for key in dict_:
      4     del dict_[key]
RuntimeError: dictionary changed size during iteration

改正方法如下:

dict_ = {'spam': 'eggs'}

for key in list(dict_):
    del dict_[key]

2.2.5.Late binding

这个问题就是所谓的后期绑定,什么是后期绑定?请看下面的例子:

1
2
3
4
5
6
7
eggs = [lambda a: i * a for i in range(3)]
for egg in eggs:
    print(egg(5))
# 理想结果
# 0
# 5
# 10
10
10
10

果然现实和理想是有差别的,为什么会造成这样的结果呢,是因为在匿名函数中i的值是2,可是明明分别循环了0、1、2。 你需要知道,定义一个函数后,函数内的变量并不是立刻就把值绑定了,而是在被调用的时候才会去查找变量,所以等egg(5)去查找i这个变量的时候,i=5,于是就造成了后期绑定这个陷阱。 要解决这个问题很简单,只需要在出现后期绑定前提前对变量进行赋值操作即可

1
2
3
4
# 粗暴点可以直接这么来
eggs = [lambda a, i=i: i * a for i in range(3)]
for egg in eggs:
    print(egg(5))
0
5
10
1
2
3
4
# 优雅点
eggs = (lambda a: i * a for i in range(3))
for egg in eggs:
    print(egg(5))
0
5
10
1
2
3
4
5
# 高级语法这么来
import functools
eggs = [functools.partial(lambda i, a: i * a, i) for i in range(3)]
for egg in eggs:
    print(egg(5))
0
5
10

总之,以上作为参考,能解决就好。

3.总结

本节笔记主要记录python一些优雅的写法以及陷阱。