容器序列和扁平序列

  • 容器序列list, tuple, collections.deque这些容器内能存放不同数据类型的数据
  • 扁平序列str, bytes, bytearray, memoryviewarray.array只能存放同一数据类型

容器内容的可变与不可变

  • 可变: list, bytearray, array.array, collections.deque, memoryview
  • 不可变tuple, str, bytes

列表推导

列表推导(list comprehension)简称为listcomps

1
2
3
4
L = [x for x in range(100)]
M = [x*y for x in range(10) for y in range(11, 20) ]
print(L)
print(M)
  • 虽然老生常谈了,但是额外说一句从后往前读非常容易理解
  • 使用原则:用列表推导来创建新的列表,并且尽量保持简短
  • python会忽略代码里的[], {}, ()中的换行
  • 列表推导不一定比map()filter()的组合速度慢

生成器表达式

  • 生成器表达式的语法跟列表推导差不多,只不过是把方括号[]变成()
  • 生成器逐个产生元素, 不会像列表推导一样一次性占用内存, 因此使用生成器表达式可以一定程度上节省内存

*运算符用于拆包

*运算符可以把一个可迭代对象拆开为函数的参数, 用*args来获取不确定数量的参数也是一种经典的写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
a, b, *rest = range(5)
print(a, b, rest)
a, b, *rest = range(3)
print(a, b, rest)
a, b, *rest = range(2)
print(a, b, rest)
a, *b, c, d = range(5)
print(a, b, c, d)
*a, b, c, d = range(5)
print(a, b, c, d)

用python打印表格的套路(str.format()函数)

点击链接

浮点数指定输出位数以及正负号

1
2
3
4
5
6
7
8
9
fmt = '{:.2f}'  # 保留两位小数
print(fmt.format(3.1415926))
>>>3.14

fmt = '{:+.2f}'  # 显示正负号
print(fmt.format(3.1415))
print(fmt.format(-520.1314))
>>>+3.14
>>>-520.13

指定长度输出与对齐方式, 格式{:*>n}

  • >: 右对齐(即靠右放置,左边填充)
  • <: 左对齐(即靠左放置, 右边填充)
  • ^: 中间对齐(即居中放置, 两遍填充)
  • n: 指定的长度
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fmt = '{:x>4}'      # 长度为4,靠右放置,左填充x
print(fmt.format(2))
>>>xxx2

fmt = '{:<4}|'      # 长度为4,靠左放置,右填充空白(使用|仅仅用来比较,方便演示)
print(fmt.format(2))
>>>2   |

fmt = '|{:^5}|'    # 中间对齐
print(fmt.format(123))
>>>| 123 |

以此基础可以打印漂亮的表格

1
2
3
4
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
print(fmt.format('Beijing', 1.234, 5.6789))
print(fmt.format('ShangHai', 12.3456, 789.012))

顺便发现了用来打印表格的库prettytable

namedtuple

创建一个namedtuple需要两个参数,一个是类名,另一个是类的各个属性的名字。后者可以是数个字符串组成的可迭代对象,或者是由空格分隔开的属性名的字符串

1
2
3
4
5
from collections import namedtuple
Point = namedtuple('Point', 'x y')
p1 = Point(1, 2)
print(p1)
>>>Point(x=1, y=2)

等价于

1
2
3
4
5
6
from collections import namedtuple
li = ['x', 'y']
Point = namedtuple('Point', li)
p1 = Point(1, 2)
print(p1)
>>>Point(x=1, y=2)

切片

在pyton中支持切片的有list, tuple, str 切片的格式为DS[start:stop:step], 左闭右开, 长度为stop - start, 每间隔step位取DS内的元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
li = list(range(10))
L1 = li[:3]            # 被取元素的下标范围为[0, 3)
print(L1)
>>>[0, 1, 2]

li = list(range(10))
L2 = li[3:]            # 被取元素的下标范围为[3, 10)
print(L2)
>>>[3, 4, 5, 6, 7, 8, 9]

li = list(range(10))
L3 = li[::2]           # 每间隔2取元素
print(L3)
>>>[0, 2, 4, 6, 8]

li = list(range(10))
L4 = li[::-1]         # 逆置(reverse)
print(L4)
>>>[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

s1 = 'I love you'
print(s1[::-1])
>>>uoy evol I

给切片赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
li = list(range(10))
li[2:5] = [20, 30]
print(li)
li[2:5] = [20, 30, 40]
print(li)
>>>[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>>[0, 1, 20, 30, 40, 6, 7, 8, 9]

li = list(range(10))
del li[3:]
print(li)
>>>[0, 1, 2]

序列的增量赋值*=+=

类能实现+=操作需要实现魔法函数__iadd__(就地加法), 如果没有实现将退一步调用__add__ 如果a实现了__iadd__方法, 则a += b将原地进行, 即将b加入到a中, 如果没实现则a = a + b, 之前的a的内存被释放(不同id)

python自带的可变容器都支持+=*=操作

*=对应__imul__, 思想类似

一个有意思的例子:

1
2
tup = (1, 2, [3, 4])
tup[2] += [5, 6]

运行上面的代码会发生什么?

上面的例子告诉我们:

  • 不要把可变对象放在元组里
  • 增量赋值不是一个原子操作, 虽然抛出了异常但还是完成了操作

list.sort()和内置函数sorted()

list.sort()是就地排序, 返回值是None(一种思想), 从python3.4开始不再支持list.sorted sorted()会新建一个列表返回 两者都有两个可选的关键字参数, reversekey, 重点讲key, key为一个只有一个参数的函数, 类似于c++里的cmp, 只传递函数名, 省略括号

bisect

bisect.bisect(haystack, needle), 在haystack(干草堆)里搜索needle(针)的位置, needle插入到这个位置后, haystack还能保持升序. 要求haystack原本有序, 原理为二分查找

bisect.insort(haystack, needle)为找到位置后并插入

不要滥用列表list

  • 存放大量的浮点数, array的效率更高, 同理, 如果我们需要一个只包含数字的列表, 那么array.array比list更高效
  • 频繁的对序列做先进先出操作, deque(双端队列)的速度更快

书上一个给人启发的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from array import array
from random import random

floats = array('d', (random() for i in range(10**7)))
fp = open('floats.bin', 'wb')
floats.tofile(fp)
fp.close()

floats2 = array('d')
fp = open('floats.bin', 'rb')
floats2.fromfile(fp, 10**7)
fp.close()

print(floats == floats2)

memoryview

deque

list删除第一个元素或者在第一个元素之前添加一个元素很费时, O(n)? 推荐使用deque, deque是线程安全的

本章的一些不好起标题的知识点

  • 交换两个变量的值a, b = b, a

  • 如果做的是国际化软件, 那么_可能就不是一个理想的占位符, 因为它是gettext.gettext函数的常用别名