Python数据类型之“序列概述与基本序列类型(Basic Sequences)”

时间:2023-04-10 14:57:26

序列是指有序的队列,重点在"有序"。

一、Python中序列的分类


Python中的序列主要以下几种类型:

  • 3种基本序列类型(Basic Sequence Types):list、tuple、range
  • 专门处理文本的附加序列类型(Text Sequence Types):str
  • 专门处理二进制数据的附加序列类型(Binary Sequence Types): bytes、bytearray、memoryview

按照序列是否可被改变分类:

  • 可变序列: list
  • 不可变序列:tuple、str

二、Python中序列支持的操作


1.通用序列操作

这里说的通用序列操作是指大部分可变序列与不可变序列都支持的操作。一般操作包括 增、删、改、查,但是这里说的是包括不可变序列也支持的通用操作,因此只能是“查”操作。

符号说明:

符号 说明
s,t 表示相同类型的序列
n,i,j,k 表示整数数值
x 表示序列s中满足条件约束的任意类型的元素
in(被包含) 和 not in 具有与比较操作相同的优先级
+(连接)和*(重复) 具有与相应数字操作相同的优先级。

序列通用操作及结果说明:

操作 结果
x in s 如果序列s中包含x对象则返回True,否则返回False
x not in s 如果序列s中不包含x对象则返回True,否则返回True
s + t 对序列s和序列t做连接操作
s * n 或 n * s 等价于 n个s相加
s[i] 表示序列s的第i个元素,i初始值为0
s[i:j] 序列s从下标i到下标j的切片(包含s[i],但不包含s[j])
s[i:j:k] 序列s从下标i到下标j的切片,且步长为k
len(s) 序列s的长度
min(s) 序列s中的最小值
max(s) 序列中的最大值
s.index(x[, i[, j]]) x在序列s中从下标i开始到下标j之前范围内第一次出现的位置
s.count(x) x在序列s中出现的总次数

说明:

a) 对于序列来说,其元素的数字类型是不做严格区分的,如True=1=1.0,False=0=0.0;

b) 相同类型的序列也支持比较操作,特别是tuple和list是通过比较对应元素的字典顺序来进行比较的。这意味着要判断两个序列相等,就需要这两个序列中的每个元素都相等,并且这两个序列必须是相同类型且长度相等。

注意:

a) 虽然in 和 not in操作只用于一般情况下的简单容器测试,但一些专用序列(如str,bytes和bytearray)也用于子序列测试。

>>> "ll" in "hello"
True

b) 如果s * n中n小于0,则n会被当做0看待;s * 0的结果是产生一个与s相同类型的空序列。

>>> "ss" * -2
''
>>> ["Tom", "Peter", "Jerry"] * -2
[]
>>> ("Tom", "Peter", "Jerry") * -2
()

c) 对于s * n操作,s序列中的元素没有被复制,他们只是被引用了多次。

>>> lists = [['a']] * 3
>>> lists
[['a'], ['a'], ['a']]
>>> lists[0].append('b')
>>> lists
[['a', 'b'], ['a', 'b'], ['a', 'b']]

d) 对于序列的切片操作s[i:j[:k]],如果i或j负数,则索引是相对于字符串的尾部来计算的。如果i是负数,则i相当于len(s)+i,如果j是负责,则j相当于len(s)+j。

>>> [0,1,2,3,4,5,6,7,8,9][-1]
9
>>> [0,1,2,3,4,5,6,7,8,9][-5:-1]
[5, 6, 7, 8]
>>> [0,1,2,3,4,5,6,7,8,9][1:-1]
[1, 2, 3, 4, 5, 6, 7, 8]

e) 还是对于序列的切片操作s[i:j[:k]],其中i与j的值有如下几种情况:

  • 如果i或j为负数,则先替换为len(s)+i或len(s)+j再进行如下比较;
  • 如果i或j大于len(s),则其值取len(s);
  • 如果i被忽略或为None,则其值取0;
  • 如果j被或略或为None,则其值取len(s);
  • 如果i的值比j大,则切片结果为空序列。
>>> s = (0,1,2,3,4,5,6,7,8,9)
>>> len(s)
10
>>> s[6:12]
(6, 7, 8, 9)
>>> s[:5]
(0, 1, 2, 3, 4)
>>> s[5:]
(5, 6, 7, 8, 9)
>>> s[9:5]
()

如果步长k被指定,则切片结果中的元素为i,i+k,i+2k,i+3k,...到j的前一个元素停止。k的值不能为0,如果k为None则其值取1。

>>> s[1::2]
(1, 3, 5, 7, 9)
>>> s[0::2]
(0, 2, 4, 6, 8)
>>> s[0::0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: slice step cannot be zero

f) 连接不可变序列时总是会导致产生一个新的对象。这意味着通过重复连接构建序列将会在在总序列长度中具有二次运行时成本。要获得线性运行成本,必须切换到以下选项之一:

  • 如果连接str对象,可以构建一个列表,并在结尾使用str.join()方法进行连接;或者写入io.String()实例并在完成时检索其值;
  • 如果连接bytes对象,可以类似地使用bytes.join()方法或io.BytesIO,或者可以使用bytearray对象进行就地连接;bytearray是可以变的,并且具有有效的覆盖分配机制;
  • 如果连接tuple对象,请使用扩展list的方式替代;
  • 对于其他类型,请查看相关类文档;

g) 一些序列类型(例如 range)仅支持遵循特定模式的元素序列,因此不支持序列的连接和重复操作。

h) 对于s.index(x[, i[, j]])操作,当在序列s中找不到x元素时,index会抛出ValueError。另外,附加参数i,j允许对该序列的子序列进行有效的查找,这大致相当于s[i,j].index(x),但是不会拷贝任何数据且返回的索引值是相对于序列的开始位置而不是相对于切片的开始位置。

2. 可变序列类支持的型操作

这里来说下可变序列类型支持,而不可变序列类型不支持的操作。在序列通用操作中主要说明的是“查”操作,这里要说的是可变序列的 "增"、“删”、“改”操作。

符号说明:

符号 说明
s 表示一个可变序列类型的实例
t 表示任何一个可迭代对象
x 表示序列s中满足条件约束的任意类型的元素(例如,bytearray只接受满足0 <= x <=255约束的整型值)

可变序列支持的操作及结果说明:

操作 结果
s[i] = x 将序列s中小标为i的元素用x替换
s[i:j] = t 将序列s中从i到j的切片用可迭代对象t的内容替换
s[i:j:k] = t s[i:j:k]中的元素用可迭代对象t的内容替换
s *= n 更新序列s为s的n次重复的结果
del s[i:j] 删除序列s中从i到j的切片,等价于 s[i:j] = []
del s[i:j:k] 从序列s中删除s[i:j:k]中的元素
s.pop() / s.pop(i) 获取序列s中下标为i的元素,并从序列s中删除该元素;i默认为-1,即默认删除并返回序列s的最后一个元素
s.remove(x) 从序列s中移除第一个等于x(即:s[i] == x )的元素;如果x在序列s中不存在,则会抛出ValueError
s.clear() 移除序列s中的所有元素,等价于 del s[:]
s.append(x) 将x追加到序列s的末尾,等价于 s[len(s):len(s) = [x]]
s.extend(t) or s+=t 将可迭代对象t中的元素拼接到序列s的末尾,大部分时候等价于 s[len(s):len(s)] = t
s.insert(i,x) 在序列s中下标为i的位置插入x
s.copy() 创建一个序列s的浅拷贝,等价于 s[:]
s.reverse() 反转序列s中元素的位置,该方法直接对序列s本身做修改(可以节约空间),不会返回被反转后的序列

注意:

a) 可变序列的clear()和copy()方法是Python 3.3中新加的方法,是为了与dict、set这些不支持切片操作的容器所提供的接口保持一致性。

b) 对于 s *= n操作,如果n小于0或等于0,序列s将被清空;另外如果n大于1,序列s并没有被复制,它们只是被引用了多次,这与序列通用操作中的s * n是一样的。

3. 不可变序列支持的操作

不可变序列类型通常实现而可变序列没有实现的唯一操作是对内建hash()方法的支持。对内建hash()的支持允许不可变序列(例如tuple)用作dict的键并存储在set和frozenset的实例中。如果尝试hash一个包含不可被hash的数据的不可变序列会导致TypeError错误。

三、Python中的基本序列(basic sequences)

Python中的基本序列类型包括: list、tuple、range,而str属于特殊序列类型,专门用于处理文本序列,这个后面单独进行说明。

1. List(列表)

Python中的列表是可变序列,通常用于存储相同类型的数据集合,当然也可以存储不同类型数据。Python中的列表表现形式有点像其他语言中的数组:列表中的元素是用方括号[]括起来,以逗号进行分割。

list类构建函数

class list([iterable])  # 这里的方括号表示iterable是可选项

list的创建方式

  • 使用方括号,用逗号分隔各条目:[],['a'], [a, b, c]
  • 使用类型构造函数:list(), list(iterable)
  • 使用列表生成式:[x for x in iterable]

列表构造函数list(iterable)会创建一个与可迭代对象iterable中的条目及条目顺序都相同的列表。可迭代对象iterable可以是一个序列(sequence)、一个支持迭代操作的容器(container),也可以是一个迭代器对象(iterator object)。如果iterable已经是一个list,则创建一个copy并返回,类似于iterable[:]。如果没有指定参数,列表构造函数会创建一个新的空list, []。

创建list示例:

>>> list1 = []  # 空列表
>>> list1
[]
>>> list2 = ["Tom", "Jerry", "Lucy", "Peter"] # 非空列表
>>> list2
['Tom', 'Jerry', 'Lucy', 'Peter']
>>>
>>> list3 = list() # 列表构造函数创建空列表
>>> list3
[]
>>> list4 = list(list2)
>>> list4
['Tom', 'Jerry', 'Lucy', 'Peter']
>>>
>>> list5 = [x for x in list2] # 列表生成式
>>> list5
['Tom', 'Jerry', 'Lucy', 'Peter']

list实现了所有通用(common)序列操作和可变序列(mutable sequence)操作,此外,list还提供了一些附加方法。

list通用序列操作示例

>>> s = [1, 2, 3, 'a', 'b', 'c']
>>> 1 in s # 包含判断
True
>>> 1.0 in s # 数字不严格区分类型(1==1.0==True)
True
>>> 1 not in s # 不包含判断
False
>>> s + ['d', 'e', 'f'] # 拼接操作
[1, 2, 3, 'a', 'b', 'c', 'd', 'e', 'f']
>>> s * 2 # 重复2次
[1, 2, 3, 'a', 'b', 'c', 1, 2, 3, 'a', 'b', 'c']
>>> s[3] # 获取下标为3的条目
'a'
>>> s[3:6] # 获取序列s的切片,下标分别为 3,4,5
['a', 'b', 'c']
>>> s[1:6:2] # 获取序列s的切片,步长为2,下标分别为 1,3,5
[2, 'a', 'c']
>>> len(s) # 获取序列长度
6
>>> min(s) # min()和max()的参数中,数据类型需要有可比性
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() < int()
>>> max([1, 2, 3, 4.0, 5.0])
5.0
>>> min([1, 2, 3, 4.0, 5.0, False])
False
>>>
>>> [1, 2, 3, 4, 2, 5, 6, 3, 2].index(2) # 获取2在序列s中第一次出现的下标位置
1
>>> [1, 2, 3, 4, 2, 5, 6, 3, 2].index(2, 3) # 获取序列s从下标3开始查找2第一次出现的下标位置
4
>>> s.count(2) # 统计2在序列s中出现的次数
1
>>> [1, 2, 3, 4, 2, 5, 6, 3, 2].count(2)
3
>>> len([1, 2, 3, 4, 5]) # 获取序列长度
5

list可变序列操作示例

>>> s = [1, 2, 3, 4]
>>> s
[1, 2, 3, 4]
>>> s.append(5) # 向list末尾追加一个条目
>>> s
[1, 2, 3, 4, 5]
>>> s.insert(0,0) # 向list开始位置插入一个条目
>>> s
[0, 1, 2, 3, 4, 5]
>>> s.extend([6, 7, 8, 9]) # 扩展list, 向list末尾拼接多个条目
>>> s
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> s[9] = 99 # 将list中下标为9的条目替换为99
>>> s
[0, 1, 2, 3, 4, 5, 6, 7, 8, 99]
>>> s[6:9] = ['a', 'b'] # 将list中范围为从6到9(不包含9)的切片替换为['a', 'b']
>>> s
[0, 1, 2, 3, 4, 5, 'a', 'b', 99]
>>> s[6:10]=['A','B'] # 同上,此处10为list的长度,表示切片范围是到list末尾的
>>> s
[0, 1, 2, 3, 4, 5, 'A', 'B']
>>> s.pop() # 移除list末尾的条目
'B'
>>> s
[0, 1, 2, 3, 4, 5, 'A']
>>> s.pop(5) # 移除list中下标为5的条目
5
>>> s
[0, 1, 2, 3, 4, 'A']
>>> s.remove('A') # 移除list中的指定条目'A'
>>> s
[0, 1, 2, 3, 4]
>>> del s[1:5:2] # 删除list中下标为 1,3 的条目
>>> s
[0, 2, 4]
>>> s.reverse() # 将list中元素位置进行反转,list本身发生改变
>>> s
[4, 2, 0]
>>> s.copy() # list浅拷贝,list本身不发生改变
[4, 2, 0]
>>> s
[4, 2, 0]
>>> s *= 2 # 相当于 s = s * 2
>>> s
[4, 2, 0, 4, 2, 0]

list支持的额外操作

sort(*, key=None, reverse=None)

此方法仅使用 < 符号进行列表项目之间的比较,即默认对列表进行升序排序。排序过程中的异常不会被捕获,也就是说如果任何比较操作失败,整个排序操作将失败,并且列表可能保留部分已修改状态。

sort()只能通过关键字(仅限关键字参数)传递的两个参数如下:

  • key : 指定一个函数,该函数用于从每个列表元素提取用于进行比较操作的键,如key = str.lower。列表中每一个条目对应的键都会被计算一次,然后用于整个排序过程。默认值None表示列表条目将会被直接排序,而不会计算出一个单独的用于比较的键值。
  • reverse : 是一个布尔值,如果其值为True,则列表中的元素将会按照比较方法的反序进行排序。

说明:

sort()方法会直接修改list,这在对大列表进行排序时可以节约空间;该方法的副作用是不会返回排序后的list,可以使用sorted()显示请求一个新的排序后的list实例;

sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list

sort()方法保证稳定性。如果能保证两个比较相当的元素不会改变相对位置,那么这个排序就是稳定的,这有助于通过多个条件进行排序,比如 先按部门排序,然后再按工资级别排序。

CPython实现细节:当一个列表正在被排序时,如果尝试修改或检测列表,效果是列表未定义。Python的C实现使得列表在持续时间内显示为空,并且如果它检测到列表在排序期间已发生改变,则会抛出ValueError错误。

>>> s = ['A', 'b', 'C', 'd', 'e']
>>> s.sort()
>>> s
['A', 'C', 'b', 'd', 'e']
>>> s.sort(key=str.lower)
>>> s
['A', 'b', 'C', 'd', 'e']
>>> s.sort(key=str.upper)
>>> s
['A', 'b', 'C', 'd', 'e']
>>> s.sort(key=str.upper, reverse=True)
>>> s
['e', 'd', 'C', 'b', 'A']
>>> s.sort(reverse=True)
>>> s
['e', 'd', 'b', 'C', 'A']
>>> sorted(s) # sorted()可以对任何可迭代对象进行排序,不会对原数据进行修改且会返回一个排序后的实例
['A', 'C', 'b', 'd', 'e']
>>> s
['e', 'd', 'b', 'C', 'A']
>>>

2.Tuple(元祖)


Tuple是不可变序列,通常用于存储异构数据集合,例如由内置函数enumerate()产生的2元组。元祖还用于同构数据的不可变序列的情况,例如允许在set或dict实例中存储。

tuple类构建函数

class tuple([iterable])  # 此处的方括号表示iterable是可选参数

tuple的构建方式

  • 使用一对小括号表示空元组:()
  • 对单个元组要使用逗号结尾:a, 或 (a, )
  • 多个条目要使用逗号分隔:a, b, c 或 (a, b, c)
  • 使用内置函数tuple() 或 tuple(iterable)

tuple构造函数tuple(iterable)会创建一个与可迭代对象iterable中的条目及条目顺序都相同的元祖。可迭代对象iterable可以是一个序列(sequence)、一个支持迭代操作的容器(container),也可以是一个迭代器对象(iterator object)。如果iterable已经是一个tuple,则直接返回这个tuple。如果没有指定参数,元组构造函数会创建一个新的空tuple, ()。

说明:

实际上,是逗号产生了一个元祖,而不是小括号。除了空元组的情形或者需要避免语法模糊的时候外,小括号是可选的。例如f(a, b, c)是具有3个参数的函数调用,而f((a, b, c))是以3元祖作为唯一参数的函数调用。

tuple创建示例

>>> 1,  # 有逗号结尾表示元组
(1,)
>>> (1, )
(1,)
>>> 1 # 没逗号结尾表示数字
1
>>> (1)
1
>>> () # 空数组
()
>>> tuple()
()
>>> tuple([1, 2, 4]) # 非空数组
(1, 2, 4)
>>> 1, 2, 3
(1, 2, 3)
>>> (1, 2, 3)
(1, 2, 3)

tuple是不可变序列,它支持所有通用序列操作(与list一致,此处不再给出示例),但不支持可变序列操作。

3.range(范围)

range类型表示一个不可变的数字序列,通常用于在for循环中循环特定次数。

range类构建函数

class range(stop)
class range(start, stop[, step])
start:表述数字序列开始值,如果该参数没有被提供则值为0
stop: 数字序列结束值
stop: 数字序列步长,如果该参数没有被提供则值为1

关于start、stop、step参数值的说明:

  • 这些参数必须是整型值(内建 int或者任何实现了__index__ 特殊方法的任意对象)
  • 如果start参数被忽略,其默认值为0;如果step参数被忽略,其默认值为1;如果step为0,则会抛出ValueError错误
  • 如果step参数为正数,那么range类型对象r的内容公式为:r[i] = start + step*i,约束条件为:i >= 0 且 r[i] < stop
  • 如果step参数为负数,那么range类型对象r的内容公式仍然为:r[i] = start + step*i,但是约束条件为:i >= 0 且 r[i] > stop
  • 如果r[0]不满足约束条件,range对象的值将为空
  • range对象可以包含绝对值大于sys.maxsize的值,但是某些功能(如:len())可能会引发OverflowError

range示例

>>> list(range(10))  # start没有指定,默认值为0,即start=0,end=10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(1,11)) # start=1, end=11
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(0, 11, 2)) # 要指定step就必须要指定start
[0, 2, 4, 6, 8, 10]
>>> list(range(0, -10, -1)) # step为负数
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
>>> list(range(0))
[]
>>> list(range(10, 5)) # 如果end < start ,则range对象为空
[]

这里需要注意一下,Python 2.x中的range()函数返回的是一个list类型,而Python 3.x中的range()函数返回的是一个range类型。

Python 2.x

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

Python 3.x

>>> range(10)
range(0, 10)

range实现了除连接(concatenation)和重复(repetition)之外的所有通用序列操作,这是由于range对象只能表示遵循严格模式的序列,而连接和重复操作通常会违反这个模式。

range相对于常规list和tuple的优点在于:无论它表示的范围的大小是多少,它始终占用相同(小)量的内存。这是因为它只需要存储 start、end和step这3个值,然后根据需要计算各个条目和子范围。

说明:

测试range对象是否相等与序列一样通过 == 和 != 进行比较,也就是说,如果两个range对象表示相同值的序列就被认为是相等的。需要注意的是,比较相等的两个range对象可能具有不同的start、end、step参数值,例如 (0) == (2, 1),又例如 (0, 3, 2) == (0, 4, 2)