# python 建议

# 培养 Pythonic 思维

# f-string 插值格式字符串

格式化操作符%,常用来格式化字符串,左边的部分是格式化字符串,右边的部分是多个值构成的元组或者字典等。

a = 0b10111011
b = 0xc5f
print('Binary is %d, hex is %d' % (a, b))
1
2
3

它有四个缺点:

  1. 如果右侧元组里面的元素位置变动,可能导致左侧的类型对应不上而报错。(或者左侧变动,右侧不变)
  2. 如果格式化的元素比较多或者需要对某些元素做一些处理,那么整个语句会特别长,甚至展示得很混乱。
  3. 如果想用同一个值来填充格式字符串里的多个位置,那么必须在右侧的元组中相应地多次重复该值。
  4. 如果将字典使用到格式字符串中,表达式可能会特别长、混乱。

Python 3 添加了高级字符串格式化(advanced string formatting)机制。把格式有待调整的那些位置在字符串里面先用{}代替,然后按从左到右的顺序,把需要填写到那些位置的值传给format()函数,使这些值依次出现在字符串中的相应位置。

key = 'my_var'
value = 1.234

formatted = '{} = {}'.format(key ,value)
print(formatted)
1
2
3
4
5

如果右侧元组里面的元素位置变动又或者一个值填充多个位置,只需要在右侧{}中添上对应的左侧参数列表的索引。这样可以解决第 1 和第 3 缺点,但还是解决不了第 2 和第 4 个缺点

key = 'my_var'
value = 1.234

formatted = '{1} = {0}'.format(key ,value)
print(formatted)
1
2
3
4
5

Python 3.6 添加了一种新的特性,叫作插值格式字符串(interpolated format string,简称 f-string),可以解决上面提到的所有问题。

key = 'my_var'
value = 1.234

formatted = f'{key} = {value}'
print(formatted)
1
2
3
4
5

它要求在格式字符串的前面加字母f作为前缀,而在{}中直接插入目标值,这样就达到了str.format()的功能还能简化整个语句。

key = 'my_var'
value = 1.234

c_tuple = '%-10s = %.2f' % (key, value)
args_str = '{:<10} = {:.2f}'.format(key, value)

f_str = f'{key:<10} = {value:.2f}'

c_dict = '%(key)-10s = %(value).2f' % {'key': key, 'value': value}
args_dict = '{key:<10} = {value:.2f}'.format(key=key, value=value)

print(c_tuple == c_dict == f_str)
print(args_str == args_dict == f_str)
1
2
3
4
5
6
7
8
9
10
11
12
13

# unpacking、enumerate()和 zip()

访问元组里的数据,你能这样写:

item = ('Peanut butter', 'Jelly')
first = item[0]
second = item[1]
print(first, 'and', second)
1
2
3
4

其实可以不用通过索引来取,可以通过“拆分(unpacking)”对元组进行拆分或者拆包,对元素本身不造成什么影响,只是将元素里面的元素拿出来赋值给其他引用。常用于函数的返回值是一个元组,将数据打包,外界得到这个元组后就可以用 unpacking 拿到对应的变量。

item = ('Peanut butter', 'Jelly')
first, second = item # 拆分
print(first, 'and', second)
1
2
3

还有一个比较常用的就是数据交换, a, b = <某些表达式>,会先计算右侧表达式的值,例如表达式5, 8就会变为元组(5, 8),左侧的a, b就是数据结构拆分(unpacking)了,那么请看下面的数据交换。

a = 5
b = 8
a, b = b, a
print(a, 'and', b)
1
2
3
4

对于一个包含零食名称和卡路里的零食元组列表,你想把它处理成一个排行榜,你的 for 循环可能会这样写(先不考虑排序):

snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)]
for i in range(len(snacks)):
    item = snacks[i]
    name = item[0]
    calories = item[1]
    print(f'#{i+1}: {name} has {calories} calories')
1
2
3
4
5
6

我们可以使用enumerate()将这个元组列表处理成带序号惰性生成器,它生成的每一项都是一个元组,元组的第一项就是序号,元组的第二项就是原始数据的每项值。enumerate(snacks, 1)就会处理成:

(1, ('bacon', 350))
(2, ('donut', 240))
(3, ('muffin', 190))
1
2
3

再用 unpacking 对上面的每个元组进行拆分,那么 for 循环就可以改进成:

snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)]
# 第一次拆分将数字标号赋值给rank,第二次拆分是因为snacks的每一项也是元组,拆成name和calories
for rank, (name, calories) in enumerate(snacks):
    print(f'#{rank}: {name} has {calories} calories')
1
2
3
4

你可能还会注意到一点,enumerate()能很好的取代range(),它的第二个参数非常好用,如果不带第二个参数,那么序号默认从0开始。

如果我们想知道一个字符串数组谁的长度更长,你可能会用enumerate()搭配 unpacking 这样写:

names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
longest_name = None
max_count = 0
for i, name in enumerate(names):
    count = counts[i]
    if count > max_count:
        longest_name = name
        max_count = count

print(longest_name, 'and', max_count)
1
2
3
4
5
6
7
8
9
10
11

可以使用zip(),它能将多个迭代序列变成惰性生成器。zip(names, counts)就会处理成:

('Cecilia', 7)
('Lise', 4)
('Marie', 5)
1
2
3

那么 for 循环可以优化成

names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
longest_name = None
max_count = 0
for name, count in zip(names, counts):
    if count > max_count:
        longest_name = name
        max_count = count

print(longest_name, 'and', max_count)
1
2
3
4
5
6
7
8
9
10

注意:

  1. 如果提供的迭代器的长度不一致,那么只要其中任何一个迭代完毕,zip 就会停止(按最短的
  2. 如果想按最长的那个迭代器来遍历,那就改用内置的 itertools 模块中的 zip_longest 函数

# 令人迷惑的 else

else是否则的意思,但是for-elsetry-except-else语句中,这个else是 and 或者 then 的意思,这是比较令人迷惑的点。

# 例1
for i in range(3):
    print('Look', i)
    if i == 1:
        break
else:
    print('Else block!')

# 例2
for i in range(3):
    print('Look', i)
else:
    print('Else block!')

# 例3
for i in range(0):
    print('Look', i)
else:
    print('Else block!')


# 例4
flag = True
count = 1
while flag:
    if count == 3:
        flag = False
    count += 1
else:
    print('While Else block!')

# 例5
flag = True
count = 1
while flag:
    if count == 15: # 这里条件改成5和15依次试试
        break
    if count == 10:
        flag = False
    count += 1
else:
    print('While Else block!')

# 例6
while False:
    print('Never runs')
else:
    print('While Else block!')

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

看完上面的例子你应该知道,在“没有进入循环”或者“进入循环后不是由break终止循环的”两种情况下,才会走到循环的 else 块中。

  • 在循环中没有使用break语句时,就没必要使用for-elsewhile-else了。因为循环正常结束(即使循环条件在一开始或者中途变为False这都是正常结束),也会走到for-elseelse中,那还不如直接将for-elseelse语句直接去掉(再把里面的代码去掉一层缩进)。

    # 上面的例3
    for i in range(3):
        print('Look', i)
    else:
        print('1111')
    
    # 直接将for的else去掉,并把里面的语句缩进去掉一层。逻辑和效果完全一样
    for i in range(3):
        print('Look', i)
    
    print('1111')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 循环中使用到了break语句,可以使用for-elsewhile-else。循环由break中断了,那么不会走到循环的 else 语句中;有break但一直没走到break,那么就会走到循环的 else 语句中。此时你就知道了,for-elsewhile-else是来对付(应付)break的两种场景(走没走 break 语句)。

    for n in range(2, 10):
        rlt = None
        for x in range(2, n):
            if n % x == 0:
                rlt = x
                break
        if rlt:
            print(n, 'equals', rlt, '*', n//rlt)
        else:
            print(n, 'is a prime number')
    
    print('--------------------------------')
    
    for n in range(2, 10):
        for x in range(2, n):
            if n % x == 0:
                print(n, 'equals', x, '*', n//x)
                break
        else:
            print(n, 'is a prime number')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 列表与字典