【流畅的Python0202】序列构成的数组1

1. 内置序列类型概览

  • 容器序列
    能存放不同类型数据,因为存放的是任意对象的引用

  • 扁平序列
    只能存放相同类型的数据,因为存放的是值

除了list、tuple和collections.deque,其他常见的序列都是扁平序列

除了tuple、str、bytes,其他常见序列都是可变序列(可以被修改)

2. 列表推导

列表推导、生成器表达式,以及同它们很相似的集合(set)推导和字典(dict)推导,在 Python 3 中都有自己的局部作用域,因此表达式内部的变量和赋值只在局部起作用

>>> x='ABC'
>>> dummy=[ord(x) for x in x]
>>> x
'ABC'
>>> dummy
[65,66,67]

3. 生成器表达式

生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。
生成器表达式相比列表推导是更好的选择,因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,能够节省内存

colors=['black','white']
sizes=['S','M','L']
g=('%s %s' % (c,s) for c in colors for s in sizes)
for t in g:
    print(t)

4. 元组

元组不仅仅是不可变的列表,元组其实是对数据的记录:元组中的每个元素位置固定,正是这个位置信息给数据赋予了意义。如:

# 表示经纬度
lax_coordinates = (33.9425, -118.408056) 

值得注意的是,除了跟增减元素相关的方法之外,元组支持列表的其他所有方法

4.1.元组拆包

  • 最好辨认的元组拆包形式就是平行赋值,也就是说把一个可迭代对象里的元素,就像下面这段代码:
    >>> lax_coordinates = (33.9425, -118.408056)
    >>> latitude, longitude = lax_coordinates # 元组拆包
    >>> latitude
    33.9425
    >>> longitude
    -118.408056
    
    接受表达式的元组可以是嵌套的,例如:(a, b, (c, d))
  • 在元组拆包中使用 * 也可以帮助我们把注意力集中在元组的部分元素上
    >>> a, b, *rest = range(5)
    >>> a, b, rest
    (0, 1, [2, 3, 4])
    >>> a, b, *rest = range(3)
    >>> a, b, rest
    (0, 1, [2])
    >>> a, b, *rest = range(2)
    >>> a, b, rest
    (0, 1, [])
    

4.2.namedtuple

namedtuple 构建的类的实例所消耗的内存跟元组是一样的,因为字段名都被存在对应的类里面。这个实例跟普通的对象实例比起来也要小一些,因为 Python 不会用 __dict__ 来存放这些实例的属性

>>> from collections import namedtuple
>>> City = namedtuple('CITY', 'name country population coordinates') # 注意类名和实例名
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo
CITY(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))

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

5. 切片

为什么切片和区间会忽略最后一个元素

  1. 快速看出切片和区间里有几个元素
  2. (下标相减)快速计算出切片和区间的长度
  3. 可以利用任意一个下标来把序列分割成不重叠的两部分

切片的操作如下:

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]

6.增量赋值

对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。但是str是一个例外,因为+=操作太普遍,因此CPython对它进行了优化。

注意不要把可变对象放在元组里,因为增量赋值不是一个原子操作:

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

7.list.sort和sorted

区别仅在于,list.sort就地排序,返回None,而sorted新建一个list,返回排序后的list

  • 参数 reverse:设为True会降序排序,默认为False
  • 参数 key:key传入某个函数,该函数会被用在序列里的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。在字符串排序中,常见的有key=str.lower(不区分大小写排序)或key=len(字符串长度排序),这个参数默认值是恒等函数

8.bisect

可以先用 bisect(haystack, needle) 查找位置index,再用 haystack.insert(index, needle) 来插入新值。但也可用 insort 来一步到位,并且后者的速度更快一些

>>> import bisect
>>> a=[1,2,3]
>>> bisect.bisect(a,1)
1
>>> a.insert(1,1)
>>> a
[1, 1, 2, 3]
>>> bisect.insort(a,2.5)
>>> a
[1, 1, 2, 2.5, 3]

bisect 函数其实是 bisect_right 函数的别名,后者还有个姊妹函数叫 bisect_leftbisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素会被放置于它相等的元素的前面,而 bisect_right 返回的则是跟它相等的元素之后的位置

用例(分数评级):

'''
(-∞,60) F
[60,70) D
[70.80) C
[80,90) B
[90,+∞) A
'''
>>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
...     i = bisect.bisect(breakpoints, score)
...     return grades[i]
...
>>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

posted @ 2023-03-08 21:03  backtosouth  阅读(22)  评论(0编辑  收藏  举报