3.元组

2 阅读5分钟

1. 元组基础

元组跟列表 (list) 非常相似。

  • 使用小括号 () 定义里面用逗号分隔开,然后()可以省略,但是逗号至少要有一个。 最后一个元素后面可以加逗号,也可以不加,但是只有一个元素的时间必须加,要不然就没一个,
  • 类型:<class 'tuple'>
  • 空元组:t = ()t = tuple()。空元组的布尔值为 False
  • 特性:元组属于可迭代对象,可以使用 for 循环遍历,也可以使用 innot in 判断元素是否包含在元组内。
# 拥有4个元素的元组对象
t1 = (55, 44, 33, 22)
print(type(t1))  # <class 'tuple'>
print(len(t1))   # 4

# 使用索引访问元素和切片
print(t1[0], t1[-1])  # 55 22
print(t1[0:2])        # (55, 44)
print(t1[0:4:2])      # (55, 33)

# 空元组
t2 = ()
t3 = tuple()
print(bool(t2))  # False

# 元组的加法和乘法
t1 = (55, 44)
t2 = (33, 22)
t3 = t1 + t2     # (55, 44, 33, 22)
t5 = t1 * 3      # (55, 44, 55, 44, 55, 44)

# 包含关系
t6 = (55, 44, 33, 44)
print(44 in t6)      # True
print(11 not in t6)  # True

定义元组可以省略括号:

t1 = 18, 'k66', 3.14
t2 = (18, 'k66', 3.14)
t3 = 18, 'k66', 3.14,
t4 = (18, 'k66', 3.14, )
# 以上四种方式定义的都是元组 (18, 'k66', 3.14)

定义单元素元组时,必须在元素后面加上逗号 ,,否则括号会被当作普通的数学运算符。

t1 = (55)
print(t1, type(t1))  # 55 <class 'int'>

t3 = (55,)
print(t3, type(t3))  # (55,) <class 'tuple'>

t6 = 55,
print(t6, type(t6))  # (55,) <class 'tuple'>

t4 = (55) * 3
print(t4, type(t4))  # 165 <class 'int'>

t5 = (55,) * 3
print(t5, type(t5))  # (55, 55, 55) <class 'tuple'>

2. 元组推导式

使用 ( ) 包含的推导式生成的不是元组,而是生成器(Generator)。如果要生成元组,需要使用 tuple() 函数。比方说 tuple(i for i in range(5))

# 这是一个生成器
t1 = (i for i in range(5))
print(type(t1))  # <class 'generator'>

# 生成元组
t2 = tuple(i for i in range(5))
print(t2)        # (0, 1, 2, 3, 4)
print(type(t2))  # <class 'tuple'>

3. 元组的解包 (Unpacking)

元组可以通过解包将元素赋值给多个变量。age, name, male = (20, 'MJ', True)

t = (20, 'MJ', True)
age, name, male = t
print(age, name, male)  # 20 MJ True

# divmod() 返回包含商和余数的元组
t = divmod(13, 4)  # (3, 1)
div, mod = divmod(13, 4)

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

# 列表也可以进行解包
a, b, c = [55, 44, 33]

字符串格式化和 enumerate 中,实际上也经常使用元组:

name = 'MJ'
age = 18
print('我的名字是%s,今年%d岁' % (name, age))  # (name, age)就是一个元组

s = [55, 44, 33, 22, 11]
# 这里的 i,e 就是元祖
for i, e in enumerate(s):  # enumerate解包
    print(i, e)

4. 序列类型 (Sequence Type)

listtuplerangestr 都属于序列类型,它们有很多类似的序列操作:

  • list 是可变类型
  • tuplerangestr 是不可变类型

常用序列操作包括:

  • len(s): 获取长度
  • s1 + s2: 拼接(range不支持)
  • s * 3: 重复(range不支持)
  • s[i]: 获取指定索引的元素
  • s[i:j]: 获取切片
  • x in s: 判断包含关系
  • s.index(x): 获取x的索引
  • s.count(x): 统计x的数量

序列类型之间的转换

可以通过 list()tuple() 相互转换:

# 元组转列表
s1 = list((55, 44, 33))  # [55, 44, 33]
# 列表转元组
s2 = tuple([55, 44, 33]) # (55, 44, 33)

# 字符串转列表/元组
s3 = list('mj666')       # ['m', 'j', '6', '6', '6']
s4 = tuple('mj666')      # ('m', 'j', '6', '6', '6')

5. 元组的不可变性

  • 元组是不可变类型,没有 appendinsertextendpopremoveclear 等方法。
  • 支持 indexcount 方法。
  • 不能修改或删除元组中的元素(如 t[0] = 66del t[1] 会报错 TypeError)。
  • 注意:如果元组中包含了可变类型的对象(如列表),元组本身不可修改,但该可变对象内部的元素是可以修改的。这个有点像js这种const修饰的对象不可修改,但是对象的属性如果是一个对象,这个属性对象可以修改一样
t = (55, 44, [66, 77, 88], 33)
# t[1] = 11  # 会报错
t[2][2] = 99 # 允许修改元组内部列表的元素
print(t)     # (55, 44, [66, 77, 99], 33)

tuple 和 list 的选择

  • tuple 属于不可变类型,数据相对更安全,资源消耗也会更低。
  • 建议:能用 tuple 的地方尽量使用 tuple,实在不行再使用 list。(例如用 month in (3, 4, 5) 代替 month in [3, 4, 5]

6. list、tuple、 str 的加法和乘法 (+= 和 *=)

列表的就地运算

对于可变的 list+=*= 可以实现原地运算(inplace),不会生成新的列表对象,性能更好。

s = [11, 22]
print(id(s))
s += [33, 44]  # 等价于 s.extend([33, 44])
print(id(s))   # id 保持不变

s += t 的本质是执行了 s.extend(t)t 只要是可迭代对象即可。

s2 = [55, 44]
s2 += range(5)  # [55, 44, 0, 1, 2, 3, 4]

字符串和元组的运算

由于 strtuple 是不可变类型,它们的 +=*= 无法实现原地操作,每次都会生成一个新的对象。

s1 = '小码哥'
s1 += 'MJ'  # 创建了一个新字符串对象,id 发生改变

t2 = (11, 22)
t2 += (33, 44)  # 创建了一个新元组对象,id 发生改变

# 注意类型匹配:元组加法必须和元组进行
t1 = (55, 44)
# t1 += [33, 22]  # TypeError: can only concatenate tuple (not "list") to tuple