11. 对序列做切片
python 中有一种写法,可以从序列里面切割出一部分内容,获取原序列的某个子集合。可以切割内置的 list、str 和 bytes。凡是实现了 __getitem__ 和 __setitem__ 的类都可以切割。
>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> print('Middle Two: ', a[3:5])
Middle Two: ['d', 'e']
>>> print('All but ends: ', a[1:7])
All but ends: ['b', 'c', 'd', 'e', 'f', 'g']
如果是从头开始切割列表,应该省略冒号左边的下标 0 ,如果是一直取到列表末尾,应该省略冒号右侧的下标。用负数做下标,表示从列表末尾往前算:
>>> a[:]
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> a[:5]
['a', 'b', 'c', 'd', 'e']
>>> a[:-1]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> a[4:]
['e', 'f', 'g', 'h']
>>> a[-3:]
['f', 'g', 'h']
>>> a[2:5]
['c', 'd', 'e']
>>> a[2:-1]
['c', 'd', 'e', 'f', 'g']
>>> a[-3:-1]
['f', 'g']
切割出来的列表是一份全新的列表,即使把某个元素换掉,也不会影响原列表中的相应位置。
>>> b = a[3:]
>>> print('Before: ', b)
Before: ['d', 'e', 'f', 'g', 'h']
>>> b[1] = 99
>>> print('After: ', b)
After: ['d', 99, 'f', 'g', 'h']
>>> print('No change: ', a)
No change: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
切片可以出现在赋值符号的左侧,表示用右侧的元素把原列表中位于这个范围之内的元素换掉。
>>> print('Before: ', a)
Before: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> a[2:7] = [99, 22, 14]
>>> print('After: ', a)
After: ['a', 'b', 99, 22, 14, 'h']
>>> a[2:3] = [47, 11]
>>> print('After: ', a)
After: ['a', 'b', 47, 11, 22, 14, 'h']
起止位置都为空的切片如果出现在赋值符号右侧,表示给这个列表做副本,这样制作出来的新列表内容和原列表相同,但 id 不同。
>>> b = a[:]
>>> b == a
True
>>> b is a
False
12. 不要在切片里同时指定起止下标和步进
切片还有一种写法是 somelist[start:end:stride],可以很容易把奇数位置上的元素与偶数位置上的元素分别选取出来:
>>> x = [1, 2, 3, 4, 5, 6]
>>> odds = x[::2]
>>> evens = x[1::2]
>>> print(odds)
[1, 3, 5]
>>> print(evens)
[2, 4, 6]
逆序:
>>> x = b'kubernetes'
>>> y = x[::-1]
>>> print(y)
b'setenrebuk'
>>> x = '冰棒'
>>> y = x[::-1]
>>> print(y)
棒冰
UTF-8 字节数据无法逆序:
>>> w = '冰棒'
>>> x = w.encode('utf-8')
>>> y = x[::-1]
>>> z = y.decode('utf-8')
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
z = y.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 0: invalid start byte
其他举例:
>>> x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> x[::2]
['a', 'c', 'e', 'g']
>>> x[::-2]
['h', 'f', 'd', 'b']
>>> x[2::2]
['c', 'e', 'g']
>>> x[-2::-2]
['g', 'e', 'c', 'a']
>>> x[-2:2:-2]
['g', 'e']
>>> x[2:2:-2]
[]
建议不要把起止下标和步进值同时写在切片里,如果必须指定步进,尽量使用正数,而且起止下标都要留空。也可以分两步来做(一次隔位选取,一次做切割)。也可以用 itertools.isslice 方法。
13. 通过带星号的拆分操作捕获多个元素,不用切片
>>> car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
>>> car_ages_descending = sorted(car_ages, reverse=True)
>>> oldest, second_oldest, *others = car_ages_descending
>>> print(oldest, second_oldest, others)
20 19 [15, 9, 8, 7, 6, 4, 1, 0]
在使用这种写法时,至少要有一个普通的接受变量与它搭配。对于单层结构来说,同一级里面最多出现一次带星号的拆分。
14. 用 sort 方法的 key 参数来表示复杂的排序逻辑
class Tool:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __repr__(self):
return f'Tool({self.name!r}, {self.weight})'
tools = [
Tool('level', 3.5),
Tool('hammer', 1.25),
Tool('screwdriver', 0.5),
Tool('chisel', 0.25),
]
print('Unsorted:', repr(tools))
tools.sort(key=lambda x: x.name)
print('\nSorted: ', tools)
>>>
Unsorted: [Tool('level', 3.5),
Tool('hammer', 1.25),
Tool('screwdriver', 0.5),
Tool('chisel', 0.25)]
Sorted: [Tool('chisel', 0.25),
Tool('hammer', 1.25),
Tool('level', 3.5),
Tool('screwdriver', 0.5)]
?15. 不要过分依赖给字典添加条目时所用的顺序
从 Python 3.6 开始,字典会保留键值对在添加时的所用的顺序,而且 Python 3.7 版的语言规范正式确立了这条规则。而在以前的版本中,字典类型是用哈希表算法来实现的(这个算法通过内置的 hash 函数与一个随机的种子数来运行,该种子数会在每次启动 Python 解释器时确定)。这样的机制导致这些键值对在字典中的存放顺序不一定会与添加时的顺序相同。
16. 用 get 处理键不在字典中的情况,不要使用 in 与 KeyError
>>> counters = {
'pumpernickel': 2,
'sourdough': 1,
}
>>> key = 'wheat'
>>> count = counters.get(key, 0)
>>> counters[key] = count + 1
>>> counters
{'pumpernickel': 2, 'sourdough': 1, 'wheat': 1}
如果字典里保存的数据比较复杂,比如列表。
>>> votes = {
'baguette': ['Bob', 'Alice'],
'ciabatta': ['Coco', 'Deb'],
}
>>> key = 'brioche'
>>> who = 'Elmer'
>>> names = votes.get(key)
>>> if names is None:
votes[key] = names = []
>>> names.append(who)
>>> votes
{'baguette': ['Bob', 'Alice'], 'ciabatta': ['Coco', 'Deb'], 'brioche': ['Elmer']}
在 3.8 或更新的版本中可以使用赋值表达式,进一步简化代码:
>>> if (names := votes.get(key)) is None:
votes[key] = names = []
>>> names.append(who)
17. 用 defaultdict 处理内部状态中缺失的元素,不要用 setdefault
如果你管理的字典可能需要添加任意的键,那么应该考虑能否用内置的 collections 模块中的 defaultdict 实例来解决问题。
from collections import defaultdict
class Visits:
def __init__(self):
self.data = defaultdict(set)
def add(self, country, city):
self.data[country].add(city)
visits = Visits()
visits.add('England', 'Bath')
visits.add('England', 'London')
print(visits.data)
>>>
defaultdict(<class 'set'>, {'England': {'London', 'Bath'}})
18. 利用 __missing__ 构造依赖键的默认值
from collections import defaultdict
def open_picture(profile_path):
try:
return open(profile_path, 'a+b')
except OSError:
print(f'Failed to open path {profile_path}')
raise
class Pictures(dict):
def __missing__(self, key):
value = open_picture(key)
self[key] = value
return value
pictures = Pictures()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
|