1
python --version
大家一定要记住:str实例不一定非要某用一种固定的方案编码成二进制数据,bytes实例也不一定非要按照某一种固定的方案解码成字符串。要把Unicode数据转换成二进制数据,必须调用str的encode方法。要把二进制数据转换成Unicode数据,必须调用bytes的decode方法。
编写python程序的时候,一定要把解码和编码操作放在界面最外层来做,让程序的核心可以使用Unicode数据来运作,这种方法通常叫做“Unicode三明治(Unicode sandwich)”。程序的核心部分,应该用str类型来表示Unicode数据,并且不要锁定到某种字符编码上面。这样可以让程序接受许多种文本编码(例如Latin-1、ShiftJIS及Big5),并把它们都转成Unicode,也能保证输出的文本信息都使用统一标准(最好是UTF-8)编码的。
我们通常需要编写两个辅助函数(helper function)以便应对这种字符类型对应的使用情况,确保输入值类型符合开发者预期。
1
2
3
4
5
6
def to_str(bytes_or_str):
if isinstance(bytes_or_str,bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value # Instance of str
1
2
3
4
5
6
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode('utf-8')
else:
value = bytes_or_str
return value # Instance of bytes
用Python对字符串做格式化处理有四种办法可以考虑:
但其中三种有严重的缺陷。
最常用的字符串格式化是采用%格式化操作。
1
2
3
a = 0b10111011
b = 0xc5f
print('Binary is %d, hex is %d' % (a,b))
Python3添加了高级字符串格式化(advanced string formatting)机制,表达能力比老式C风格的格式化字符串要强,不再使用%操作符。
我们针对需要调整格式的这个Python值,调用内置的format函数,并把这个值所应具备的格式也传给该函数,即可实现格式化。 也可以把待调整的字符串用{}替代,按照从左到右的顺序,传给format函数。
1
2
3
4
5
6
7
8
9
10
11
12
a = 1234.5678
formatted = format(a, ',.2f') # 逗号表示千位分隔符
print(formatted)
b = 'my string'
formatted = format(b, '^20s') # ^表示居中对齐
print('*', formatted, '*')
key = 'my_var'
value = 1.234
formatted = '{} = {}'.format(key,value)
print(formatted)
Python 3.6添加了一种新的特性,叫做插值字符串(interpolated format string,简称f-string),新语法特性要求在字符串的前面加f作为前缀,这个字母b与字母r的用法类似,也就是分别表示字节形式的字符串与原始的(或者未经转义的)字符串的前缀。
完美地解决了字段名冗长的问题。
1
2
3
4
5
6
key = 'my_var'
value = 1.234
formatted = f'{key} = {value}'
print(formatted)
f_string = f'{key:<10} = {value:.2f}'
在f-string方法中,各种Python表达式都可以出现在{}里,于是这就解决了前面提到的第二个缺点。
在以上四种字符串格式化办法里,f-string可以简洁而清晰地表达出许多逻辑,成为了程序员地最佳选择。
语法简洁地Python虽然可以写出许多浓缩的句式,但应该避免让这样的写法把表达式弄得太复杂。要遵循DRY原则,也就是不要重复自己写过的代码(Don’t Repeat Yourself)。
用if/else结构写成的条件表达式,要比用or与and写成的Boolean表达式更好
Python有一种写法,叫做拆分(unpacking)。这种写法让我们只用一条语句,就可以将元组里面的元素赋值给多个变量。元组本身不能修改,而将元组拆分后赋值地变量的值是可以修改的。
通过unpacking来赋值比通过下标去访问元组的元素要更加清晰,而且这种写法所需的代码量通常较少。
1
2
3
4
5
def bubble_sort(a):
for _ in range(len(a)):
for i in range(1, len(a)):
if a[i] < a[i -1]:
a[i-1], a[i] = a[i], a[i-1]
这样写比直接赋值(利用临时变量写三行赋值代码)可读性更高,之所以这样写成立,是因为Python处理赋值操作的时候,要求先对=右侧求值,于是它会新建一个临时的元组,把a[i]与a[i-1]两个元素放到这个元组内。做完unpacking后,系统会扔掉这个临时元组。
unpacking还有一个重要的用法是可以在for循环或者类似的结构里,把复杂的数据拆分到相关的变量中。
Python的unpacking机制,可以用在很多方面,例如构建列表、给函数设计参数列表、传递关键字参数、接收多个返回值等。
enumerate能够把任何一种迭代器(itertor)封装成惰性生成器(lazy generator)。这样的话,每次循环的时候,它只需要从iterator里面获取下一个值就行了,同时还会给出本轮循环的序号,即生成器每次产生一对输出值。
enumerate函数可以用简洁的代码迭代iterator,而且可以指出这轮循环的序号。
不要先通过range指定下标的取值范围,然后用下标去访问序列,而是应该直接用enumerate函数迭代。
Python内置的zip函数可以把两个或者更多的iterator封装成惰性生成器(lazy generator)。每次循环时,会分别从这些迭代器里获取各自的下一个元素,并把这些值放在一个元组里面。而这个元组可以拆分到for语句中的变量之中。这样写出的代码比通过下标访问多个列表清晰的多。
1
2
3
4
5
6
7
8
9
10
11
12
# enumerate写法
for i, name in enumerate(names):
count = counts[i]
if count > max_count:
longest_name = name
max_count = count
# zip写法
for name, count in zip(names, counts):
if count > max_count:
longest_name = name
max_count = count
zip每次只从它封装的那些迭代器里面各自取出一个元素,所以即便源列表很长,程序也不会因为占用内存过多而崩溃。
在zip函数中:只要任何一个迭代器处理完毕,它就不再往下走了。循环的次数实际上等于最短的那份列表的长度。
如果想按最长的那个迭代器来遍历,那就改用内置的intertools模块中的zip_longest函数。
Python的循环有一项大多数编程语言都不支持的特性,即可以把else块紧跟在整个循环结构的后面。在执行完循环后继续走else的代码,如果在循环未执行完时退出循环了,那么就不会走else块的代码。
这个逻辑与普通的判断中的else块逻辑并不相同,为什么for循环走完后还要走else块呢?这样不就成了and的关系了?在循环后面写else块会让代码产生歧义,对可读性的影响大于了带来的便利性,不建议使用。
赋值表达式是python3.8引入的新语法,它会用到海象操作符(walrus operator).我们在Python中经常要先获取某个值,然后判断它是否非零,如果是就执行某段代码。对于这种用法,赋值表达式就是为了解决这个问题。
1
2
3
4
5
6
7
8
9
10
11
12
# 原写法
count = fresh_fruit.get('lemon', 10)
if count:
make_lemonade(count)
else:
out_of_stock()
# 使用了海象表达式的写法
if count:= fresh_fruit.get('lemon', 10):
make_lemonade(count)
else:
out_of_stock()
新写法虽然只省了一行,但读起来清晰很多,因为这种写法明确体现了count遍历只与if有关。这个赋值表达式先把:=右边的值赋给左边的count变量,然后对自身求值,也就是把变量的值当成整个表达式的值。
这种先赋值再判断的做法,正是还想表达式想表达的用意。
Python虽然不支持switch/case与do/while结构,但可以利用赋值表达式清晰的模拟这种逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# switch/case
if ( count:= fresh_fruit.get('lemon', 10)) >= 2:
make_lemonade(count)
elif ( count:= fresh_fruit.get('apple', 10)) >= 4:
make_cider(count)
elif count:= fresh_fruit.get('banana', 10):
make_bananas(count)
# do/while
bottles = []
while fresh_fruit := pick_fruit():
for fruit, count in fresh_fruit.items():
batch = make_juice(fruit, count)
bottles.extend(batch)