在python中,序列(sequence)是最基本的数据结构。序列中每个元素都有编号,即其位置或索引。索引从0开始。本节介绍最常用的2种python内置序列,:列表和元组。
另一种重要的序列是字符串,已经介绍过。
列表(List)和元组(Tuple)的主要不同在于:列表可以修改,元组不能。
python支持一种数据结构的基本概念,名为容器(container)。容器就是可包含其他对象的对象。两种主要的容器是序列(如列表和元组)和映射(如字典)。在序列中,每个元素都有编号,而在映射中,每个元素都有名称(也叫键)。映射将在后面的帖子介绍。有一种既不是序列也不是映射的容器,叫做集合(set),也会在后面讨论。
1. 通用的序列操作
有几种操作适用于所有序列,包括索引、切片、相加、相乘和成员资格检查。python还提供了一些内置函数,可以确定序列的长度以及找出序列中最大和最小元素。
有一个重要的操作放在后面章节介绍,它就是迭代。对序列进行迭代意味着对其每个元素都执行特定的操作。
1.1 索引
索引(indexing)是从 0 开始递增的编号,负数表示从末尾开始计数。可以使用如下的编号来访问元素:
>>> s = "Python"
>>> s[0] # 第一个字符
'P'
>>> s[3] # 第四个字符
'h'
>>> s[-1] # 最后一个字符
'n'
>>> s[-3] # 倒数第三个字符
't'
字符串就是由字符组成的序列,不同于其他一些语言,python没有专门用于表示字符的类型。因此一个字符就是只包含一个元素的字符串。
这种索引方式适用于所有序列。
如果函数返回调用一个序列,可对其直接执行索引操作。例如,如果你执行获取用户输入的年份的第四位,可以像下面这样做:
>>> fourth = input("Year:")[3]
Year:2025
>>> forth
'5'
练习1
编写一个程序,提示用户输入年份、月份和日期(年、月、日),然后按如下格式输出日期:
Year: 1974
Month (1-12): 8
Day (1-31): 16
August 16th, 1974
其中 Day 的后缀要根据 1–31 正确显示 “st, nd, rd, th”。完整题解如下:
# 将以数字指定的年、月、日打印出来
# 1. 月份名称列表
months = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
]
# 2. 日期后缀列表:1st, 2nd, 3rd, 4th…31st
endings = ['st', 'nd', 'rd'] \
+ 17 * ['th'] \
+ ['st', 'nd', 'rd'] \
+ 7 * ['th'] \
+ ['st']
# 3. 读取输入
year = input('Year: ')
month = input('Month (1-12): ')
day = input('Day (1-31): ')
# 转换为整数,便于索引
month_number = int(month)
day_number = int(day)
# 4. 获取对应的月份名称和后缀
month_name = months[month_number - 1]
suffix = endings[day_number - 1]
# 5. 打印结果
print(f"{month_name} {day_number}{suffix}, {year}")
1.2 切片
除了使用索引来访问单个元素外,还可以使用切片(slicing)来访问特定范围的元素。为此可使用2个索引,并用冒号分隔:
# 定义一个字符串
s = "abcdefg"
# 切片:从索引 2 开始(包含),到索引 5 结束(不包含)
# 也就是取 s[2], s[3], s[4]
slice_part = s[2:5]
print(slice_part) # 输出 'cde'
切片包含2个索引,其中第一个索引指定的元素包含在切片内,但是第二个索引指定的元素不包含在切片内。
灵活运用索引可以减少很多工作量,例如:
负索引访问单个元素
步长不能为0,否则无法向前移动,但可以为负数,即从右向左提取元素。
seq[-1]
:最后一个元素seq[-3]
:倒数第三个元素
取出或去掉末尾部分
seq[-N:]
:取最后 N 个元素seq[:-N]
:去掉最后 N 个元素
复制、反转序列
seq[:]
:复制整个序列(浅拷贝)seq[::-1]
:反转序列
改变步长切片
执行切片操作时可以显示或隐式指定起点和终点,但通常忽略另一个参数,即步长。步长参数在未被指定时默认为1,如果显式指定步长,如下例的2,则意味着在起点和终点之间每隔一个元素提取一个元素。
seq[::2]
:隔一个取一个(偶数索引)seq[::-2]
:从末尾开始,隔一个取一个
示例代码:
# 准备一个字符串和一个列表
s = "abcdefg"
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print("原序列:", s, lst)
# 1. 负索引访问单个元素
print("最后一个:", s[-1], lst[-1])
print("倒数第三个:", s[-3], lst[-3])
# 2. 取出或去掉末尾部分
print("最后三位:", s[-3:], lst[-3:])
print("除去最后三位:", s[:-3], lst[:-3])
# 3. 复制、反转序列
print("复制序列:", s[:], lst[:])
print("反转序列:", s[::-1], lst[::-1])
# 4. 步长切片
print("隔一个取一个 (偶数位):", s[::2], lst[::2])
print("反向隔一个取一个:", s[::-2], lst[::-2])# 负的 step 时,start 默认改为序列末尾元素
1.3 序列相加
使用加法运算符可以拼接序列。例如:
# 拼接相同类型的序列
# 示例1:拼接两个字符串
str1 = "Hello, "
str2 = "world!"
result_str = str1 + str2 # 字符串 + 字符串
print("拼接字符串结果:", result_str)
# 示例2:拼接两个列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]
result_list = list1 + list2 # 列表 + 列表
print("拼接列表结果:", result_list)
# 示例3:拼接两个元组
tuple1 = (1, 2)
tuple2 = (3, 4)
result_tuple = tuple1 + tuple2 # 元组 + 元组
print("拼接元组结果:", result_tuple)
# 尝试拼接不同类型的序列
try:
mixed_result = [1, 2, 3] + "abc" # 列表 + 字符串
except TypeError as e:
print("拼接不同类型的序列时出错:", e)
try:
mixed_result = (1, 2) + [3, 4] # 元组 + 列表
except TypeError as e:
print("拼接不同类型的序列时出错:", e)
但是,拼接不同类型的序列通常不可以,例如列表和字符串。
1.4 乘法
将序列与一个数字相乘,表示将重复这个序列x次来创建一个新的序列。例如:
# 示例1:字符串重复
string = "Hello "
multiplied_string = string * 3 # 将字符串重复3次
# 输出: 字符串重复结果: Hello Hello Hello
# 示例2:列表重复
list_example = [1, 2, 3]
multiplied_list = list_example * 2 # 将列表重复2次
# 输出: 列表重复结果: [1, 2, 3, 1, 2, 3]
# 示例3:元组重复
tuple_example = (4, 5)
multiplied_tuple = tuple_example * 4 # 将元组重复4次
# 输出: 元组重复结果: (4, 5, 4, 5, 4, 5, 4, 5)
None、空列表和初始化
空列表是使用不包含任何内容的2个方括号[]表示的。如果要创建一个可包含 10 个元素的列表,但没有任何有用的内容,可使用 [0] * 10
,这将创建一个包含 10 个零的列表。有些情况下,可以使用表示“什么都没有”的值,如表示还没有在列表中添加任何内容。Python 中,None表示什么都没有。因此,要将列表的长度初始化为 10,可像下面这样做:
>>> sequence = [None] * 10
>>> sequence
[None, None, None, None, None, None, None, None, None, None]
练习2 序列(字符串)乘法运算示例
# 在位于屏幕中央且宽度合适的方框内打印一个句子
sentence = input("Sentence: ") # 用户输入句子
screen_width = 80 # 屏幕宽度
text_width = len(sentence) # 句子的长度
box_width = text_width + 6 # 方框宽度(包括两侧的边框和填充)
left_margin = (screen_width - box_width) // 2 # 左侧边距,确保方框居中
# 打印方框
print() # 换行
print(' ' * left_margin + '+' + '-' * (box_width - 2) + '+') # 上边框
print(' ' * left_margin + '|' + ' ' * text_width + '|') # 左右边框
print(' ' * left_margin + '|' + ' ' * 2 + sentence + ' ' * 2 + '|') # 中间内容
print(' ' * left_margin + '|' + ' ' * text_width + '|') # 左右边框
print(' ' * left_margin + '+' + '-' * (box_width - 2) + '+') # 下边框
print() # 换行
输出:
+----------------------------------------+
| |
| He's a very naughty boy! |
| |
+----------------------------------------+
1.5 成员资格
要检查特定的值是否包含在序列中,可以使用运算符in,它检查是否满足指定的条件,并返回相应的布尔运算符:满足时返回True,不满足时返回False。下面是一些in运算符的使用示例:
# 示例1:检查字符是否在字符串中
permissions = 'rw'
print('w' in permissions) # 输出: True
print('x' in permissions) # 输出: False
# 示例2:检查元素是否在列表中
users = ['mlh', 'foo', 'bar']
user_input = input('Enter your user name: ')
print(user_input in users) # 根据输入判断用户是否存在
# 示例3:检查子字符串是否在字符串中
subject = '$$$ Get rich now!!! $$$'
print('$$$' in subject) # 输出: True
练习3 序列成员资格示例
#检查用户名和PIN码
database = [
['albert', '1234'],
['dilbert', '4242'],
['smith', '7524'],
['jones', '9843']
]
username = input('User name:')
pin = input('PIN code')
if [username,pin] in database:
print('Assess granted')
长度、最小值、最大值
内置函数len、min、max 是非常有用的工具,分别用于获取序列的长度、最小值和最大值。以下是这些函数的基本用法及其应用场景。最后一个例子中,调用max和min时指定的实参并不是序列,而是直接将数值作为实参。
# 示例1:使用 len 函数获取序列的长度
numbers = [100, 34, 678]
print(len(numbers)) # 输出: 3
# 示例2:使用 max 函数获取序列中的最大值
print(max(numbers)) # 输出: 678
# 示例3:使用 min 函数获取序列中的最小值
print(min(numbers)) # 输出: 34
# 示例4:直接比较多个数值(非序列形式)
print(max(2, 3)) # 输出: 3
print(min(9, 3, 2, 5)) # 输出: 2
2.列表
2.1 创建列表:函数list
使用函数list可以利用字符串创建列表:
>>> list('hello!')
['h', 'e', 'l', 'l', 'o', '!']#字符串转换为字符列表
不仅字符串,任何序列都能作为list的参数。
要将前述代码中的字符列表转换为字符串,可以使用’.join(somelist)表达式。
>>> str=''.join(a)
>>> str
'hello'
2.2基本的列表操作
2.2.1 给元素赋值
使用索引表示法,给特定位置的元素赋值:
>>> x=[1,1,1]
>>> x[1]=2
>>> x
[1, 2, 1]
注:不能给不存在的元素赋值,即列表长度为2时,不能给索引为100的元素赋值。
2.2.2 删除元素
del语句可以实现从列表中删除元素。
>>> del x[0]
>>> x
[2, 1]
2.2.3 给切片赋值
切片是一个极其强大的功能,比如:
>>> name=list('Perl')
>>> name
['P', 'e', 'r', 'l']
>>> name[2:]=list('ar')
>>> name
['P', 'e', 'a', 'r']
上述代码说明切片可同时给多个元素赋值,同时切片赋值还可以改变序列长度。
>>> name=list('abcd')
>>> name
['a', 'b', 'c', 'd']
>>> name[:]=('python')
>>> name
['p', 'y', 't', 'h', 'o', 'n']
使用切片赋值还可以在不替换原有元素的情况下插入新元素:
>>> nums=[1,5]
>>> nums
[1, 5]
>>> nums[1:1]=[2,3,4]
>>> nums
[1, 2, 3, 4, 5]
上述代码替换了一个空切片,相当于插入了一个序列。可采取相反的措施来删除切片:
>>> nums=[1,2,3,4,5]
>>> nums
[1, 2, 3, 4, 5]
>>> nums[1:4]=[]
>>> nums
[1, 5]
上述代码与del nums[1,4]是等效的。
2.3 列表方法
方法是与对象(列表、数、字符串等)联系紧密的函数。调用方法的格式是:
object.method(arguments)
方法调用与函数调用很像,只是在方法名前加上了对象和'.'。列表包含多个可以用来查看或修改其内容的方法:
1.append
append方法将一个对象附加到列表末尾。
>>> lst=[1,2,3]
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]
2.clear
clear方法清空列表内容:
>>> lst
[1, 2, 3, 4]
>>> lst.clear()
>>> lst
[]
类似于切片赋值语句lst[:]=[]。
3.copy
方法copy复制列表,这和常规复制不同,请看下面代码的区别:
>>> a=[1,2,3]
>>> b=a
>>> b[1]=4
>>> a
[1, 4, 3]#常规复制是将另一个名称关联到列表
#要让a和b指向不同的列表,就必须将b关联到a的副本
>>> a=[1,2,3]
>>> b=a.copy()
>>> b[1]=4
>>> a
[1, 2, 3]#b改变不再引起a变换
这类似于使用a[:]或list(a),它们也都复制a。
4.count
方法count计算指定的元素在列表中出现了多少次:
>>> ['to','be','or','not','to','be'].count('to')
2
>>> x=[[1,2],1,1,[2,1,[1,2]]]
>>> x.count([1,2])
1
>>> x.count(1)
2
5.extend
方法extend能够同时将多个值附加到列表末尾,可将这些值组成的序列作为参数提供给方法extend。也就是说,可以使用一个列表来扩展另一个列表。
>>> a=[1,2,3]
>>> b=[4,5,6]
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 5, 6]
虽然看起来和拼接很像,但是常规拼接的结果是返回一个新的序列,而非上述代码的被扩展序列(a)。
>>> a=[1,2,3]
>>> b=[4,5,6]
>>> a+b
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3]
6.index
index方法在列表中查找指定值第一次出现的索引。
>>> knights=['we','are','the','knights','who','say','ni']
>>> knights.index('who')
4
>>> knights.indes('herring')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'indes'. Did you mean: 'index'?
#搜索'herring'时引发了异常,因为根本没有找到这个单词
7.insert
方法insert用于将一个对象插入列表。
>>> nums=[1,2,3,4,5,6,7]
>>> nums.insert(3,'four')
>>> nums
[1, 2, 3, 'four', 4, 5, 6, 7]
8.pop
方法pop从列表中删除末尾最后一个元素,并返回这个元素。
>>> x=[1,2,3]
>>> x.pop()
3
>>> x
[1, 2]
>>> x.pop(0)
1
>>> x
[2]
注:pop是唯一既修改列表又返回一个非None值的列表方法。
使用pop可实现栈(stack)。
push和pop是栈的2种常见操作方式,python没有提供push。但可以用append来代替:
>>> x=[1,2,3]
>>> x.append(x.pop())
>>> x
[1, 2, 3]
9.remove
方法remove用于删除第一个为指定值的元素。
>>> x=['to','be','or','not','to','be']
>>> x.remove('be')
>>> x
['to', 'or', 'not', 'to', 'be']
>>> x.remove('bee')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
#无法删除列表中其他为指定值的元素
10.reverse
方法reverse按相反的顺序排列列表中的元素:
>>> x=[1,2,3]
>>> x.reverse()
>>> x
[3, 2, 1]
如果要按照相反的顺序迭代序列,可使用函数reversed。这个函数不返回列表,而是返回一个迭代器(后面章节介绍):
[3, 2, 1]
>>> list(reversed(x))
[1, 2, 3]
11.sort和sorted
方法sort用于对原列表排序,不返回任何值:
>>> x=[4,6,2,1,7]
>>> x.sort()
>>> x
[1, 2, 4, 6, 7]
因此,一种常见的错误写法是这样的:
>>> x=[4,6,2,1,7]
>>> y=x.sort()
>>> print(y)
None
正确写法是利用上面的copy方法,将y关联到x的副本,再对y进行排序:
>>> x=[4,6,2,1,7]
>>> y=x.copy()
>>> y.sort()
>>> x
[4, 6, 2, 1, 7]
>>> y
[1, 2, 4, 6, 7]
另一种方式是使用sorted函数:
>>> x=[4,6,2,1,7]
>>> y=sorted(x)
>>> y
[1, 2, 4, 6, 7]
>>> x
[4, 6, 2, 1, 7]
12.高级排序
方法sort接受2个可选参数:key和reserve。参数key接收一个函数,对序列中的每个元素调用该函数,返回值作为排序的依据,比如:
# 示例 1:按字符串长度升序排序(key=len)
words = ['aardvark', 'abalone', 'acme', 'add', 'aerate']
# key=len 表示:排序时,先调用 len(x) 得到每个元素的“键”(这里是字符串长度),
# 然后根据这个键来决定先后顺序
words.sort(key=len)
print(words)
# 输出:['add', 'acme', 'aerate', 'abalone', 'aardvark']
# 解释:'add' 长度 3,'acme'、'aerate' 长度 4,...,最长的是 'aardvark'
# 示例 2:按数值绝对值升序排序(key=abs)
nums = [4, -1, -7, 3, -2]
# key=abs 表示:排序时先用 abs(x) 计算每个元素的绝对值,再按绝对值排序
nums.sort(key=abs)
print(nums)
# 输出:[-1, -2, 3, 4, -7]
# 解释:abs(-1)=1,abs(-2)=2,abs(3)=3,abs(4)=4,abs(-7)=7,按 1,2,3,4,7 排序
参数reverse可以指定为真值True或False,指出是否要按照相反(默认是升序)的顺序对列表进行排序。
# 示例 3:按数值降序排序(reverse=True)
nums = [4, 6, 2, 1, 7, 9]
# reverse=True 表示:在完成默认(或 key 指定)的升序排序后,再把整个列表反转
nums.sort(reverse=True)
print(nums)
# 输出:[9, 7, 6, 4, 2, 1]
函数sorted()也接受参数key和reverse,与sort方法不同的是,它会返回一个新列表,且不改变原列表:
# 示例 1:基本用法,不带 key 和 reverse
data = [5, 2, 9, 1]
print(sorted(data)) # 输出 [1, 2, 5, 9]
print(data) # 原列表依旧 [5, 2, 9, 1]
# 示例 2:按绝对值升序排序(key=abs)
nums = [4, -1, -7, 3, -2]
sorted_nums = sorted(nums, key=abs)
print(sorted_nums) # 输出 [-1, -2, 3, 4, -7]
# 解释:先计算 abs(x),得到 1,2,3,4,7,再按这些值升序排列
# 示例 3:按字符串长度降序排序(key=len, reverse=True)
words = ['banana', 'pie', 'Washington', 'book']
sorted_words = sorted(words, key=len, reverse=True)
print(sorted_words) # 输出 ['Washington', 'banana', 'book', 'pie']
# 解释:先按 len(x) 排序,再整体翻转,最长的排在最前面
3.元组
3.1 创建元组
元组是不可修改的序列,常用于表示固定的数据集合。只需用逗号分隔开值就能自动创建一个元组。
>>> 1,2,3
(1, 2, 3)
>>> x=1,2,3
>>> x
(1, 2, 3)
决定生成元组的其实是逗号而不是圆括号。 圆括号是可选的,生成空元组或需要避免语法歧义的情况除外。 例如,f(a, b, c) 是在调用函数时附带三个参数,而 f((a, b, c)) 则是在调用函数时附带一个三元组。
元组还可以用圆括号括起,这也是通常采用的做法:
>>> (1,2,3)
(1, 2, 3)
空元组用2个空括号表示:
>>> ()
()
表示只包含一个值的元组,就是在它后面加上逗号。下面的代码示例中,最后2个示例创建的元组长度为1,而第一个示例根本没有创建元组。:
>>> 42
42
>>> 42,
(42,)
>>> (42,)
(42,)
创建多个相同值的元组可以用乘法(不要忽略逗号):
>>> 3*(40+2,)
(42, 42, 42)
3.2 tuple函数
函数tuple的工作原理与list相似,将一个序列作为参数,并将其转换为元组。如果参数已经是元组,就原封不动返回它。
>>> tuple([1,2,3])
(1, 2, 3)
>>> tuple('abc')
('a', 'b', 'c')
>>> tuple((1,2,3))
(1, 2, 3)
元组并不复杂,除了创建和访问其元素外,对元组的可执行操作不多。元组的访问和其他序列相同:
>>> x=1,2,3
>>> x
(1, 2, 3)
>>> x[1]
2
>>> x[:2]
(1, 2)
元组的切片也是元组。它们通常用作映射中的键以及集合的成员,而列表则不行。映射将在以后介绍。
练习题:列表合并
已知两个列表
a
和b
,其中a
包含 20 个元素,b
包含 30 个元素。请编写 Python 代码创建一个新的列表c
,使其满足:1.c 的前 20 个元素来自列表
a
;2.
c
的后 30 个元素来自列表b
。
法1:直接赋值
a = list(range(1, 21)) # 示例:a 具有 20 个元素
b = list(range(21, 51)) # 示例:b 具有 30 个元素
c = [0] * (20 + 30) # 预先创建一个长度为50的列表
for i in range(20):
c[i] = a[i]
for i in range(30):
c[i + 20] = b[i]
print("直接赋值法合并结果:", c)
法2:使用 append()
方法
# 定义两个列表
a = list(range(1, 21)) # 示例:a 具有 20 个元素 [1, 2, ..., 20]
b = list(range(21, 51)) # 示例:b 具有 30 个元素 [21, 22, ..., 50]
# 创建空列表并逐个添加元素
c = []
for item in a:
c.append(item)
for item in b:
c.append(item)
print("append() 方法合并结果:", c)
法3:使用 +
操作符
# 定义两个列表
a = list(range(1, 21)) # 示例:a 具有 20 个元素 [1, 2, ..., 20]
b = list(range(21, 51)) # 示例:b 具有 30 个元素 [21, 22, ..., 50]
# 直接使用 + 操作符合并
c = a + b
print("+ 操作符合并结果:", c)