元组——不为人知的世界
都知道元组是一个不可变的列表,但是元组真的只是一个不可变的列表么?
元组的特点
- 元组是一个不可变的列表(元组中不可进行修改操作)
- 元组是一个没有字段名的记录
不可变列表
对于元组的操作,我们应该都很熟悉,说些小冷门
对比list
不可变列表那么必不可少的和列表进行比较了
print(dir(tuple))
print((set(dir(list)) - set(dir(tuple))))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
{'sort', 'extend', 'pop', '__reversed__', '__delitem__', 'insert', '__imul__', 'remove', 'copy', '__iadd__', '__setitem__', 'append', 'reverse', 'clear'}
那就先简单的看看这些tuple没有的方法是干什么用的吧
方法名 | 作用 |
---|---|
s.sort() | 就地对s中的元素进行排序 |
s.extend(it) | 将可迭代对象it 追加给s |
s.pop([p]) | 删除最后或可选的位置p的元素 |
s.append(e) | 在尾部添加一个新元素 |
s.reverse() | 就地把s元素倒叙排列 |
s.clear() | 删除所有元素 |
s.insert(p,e) | 在位置p之前插入e |
s.copy() | 列表的浅拷贝 |
s.remove(e) | 删除s中出现的第一个e |
s._ reversed _() | 返回s的倒叙迭代器 |
s._ delitem _(P) | 将位于P位置的元素删除 |
s._ _ imul_ _(n) | s *= n 就地重复拼接 |
s._ _ iadd _ _(s2) | s += s2 就地拼接 |
s._ _ setitem _ _(p,e) | s[p] = e ,把元素e放在位置p,替代已经在那个位置的元素 |
修改元组
元组被定义为不可变的,但如果我一定要修改元组
tu = (1,2,3)
tu[1] = 'a'
# TypeError: 'tuple' object does not support item assignment
报错了,我们对比下tuple 和 list 内部封装方法的区别
import re
ret = re.findall('__(?:.*?)__',''.join((set(dir(list)) - set(dir(tuple)))))
print(ret)
['__imul__', '__delitem__', '__iadd__', '__setitem__', '__reversed__']
可以很轻易的发现tuple中没有封装 _ _ setitem _ _ 方法,双下setitem 是设定给定键的值,这是一个比较神奇的方法,
插播
魔术方法
__getitem__(self,key):返回键对应的值。
__setitem__(self,key,value):设置给定键的值
__delitem__(self,key):删除给定键对应的元素。
__len__():返回元素的数量
但是我一定要修改tuple的值,皮这一下就是很舒服
tu = (1,2,[3,4])
tu[2][1] = 1
好了,元组虽然不能修改但是我们可以借助列表来进行修改。
为什么说是在借助列表修改呢?
在内存中 先开辟了一个有3个位置的 tuple 的内存 然后将内存地址 赋值给 tu
第一个位置 存放了 1 的地址
第二个位置 存放了 2 的地址
第三个位置 存放了 list 的地址
在这个位置 的 list 中 又存放了 [3,4]
所以我们看似在修改元组,实则在修改列表
记录作用
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总和和他们的位置——似乎就可有可无。但如果把元组当作一些字段的集合,那么数量和位置信息就变得非常重要了。
lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids):
print('%s/%s' %passport)
BRA/CE342567
ESP/XDA205856
USA/31195855
这里就用元组对信息做了记录
插播
print('%s/%s' %passport) 这里之所以 参数不对应是因为 %格式运算符能匹配到对应的元组元素上
如果使用format 必须对应参数个数
print('{}/{}' .format(passport[0], passport[1]))
元组的拆包
最好辨认元组拆包形式就是平行赋值,就是说把一个可迭代对象的元素,一并赋值到由对应的变量组成的元组中
举个栗子
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
使用for进行拆包
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for country, _ in traveler_ids:
print(country)
插播
我们使用_进行占位符,筛除我们不需要的信息,但是在做国际化软件_就不是一个很好的选择
优雅的交换变量值
a, b = b, a
*号的使用
用*号传参
>>>divmod(20, 8)
(2,4)
>>>t = (20, 8)
>>>divmod(*t)
(2,4)
>>>quotient, ramainder = divmod(*t)
>>>quotient, ramainder
(2,4)
用*号处理元素
在python3中,用*获取不确定数量的参数这个概念被扩展到了平行赋值中
>>> a, b, *c = range(5)
>>> a, b, c
(0, 1, [2, 3, 4])
>>> *a, b, c = range(5)
>>> a, b, c
([0, 1, 2], 3, 4)
>>> a, *b, c = range(5)
>>> a, b, c
(0, [1, 2, 3], 4)
在平行赋值中, *前缀只能用在一个变量名前,但是可以是任意位置
>>> *a, b, *c = range(5)
File "<stdin>", line 1
SyntaxError: two starred expressions in assignment
注
元组的拆包可以应用到任何可迭代对象上,唯一的要求就是:可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。除非我们用*来表示忽略多余的元素
嵌套元组拆包
metro_areas = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.825, (28.689722, -99.691667)),
('Mexico City', 'MX', 36.933, (19.689722, -74.691667)),
('Sao Paulo', 'BR', 19.933, (-23.689722, -46.691667)),
]
print('{:15} | {:^9} | {:^9}'.format('','lat.','long.'))
fmt = '{:15} | {:^9.4f} | {:^9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas:
if longitude < 0:
print(fmt.format(name, latitude, longitude))
| lat. | long.
Delhi NCR | 28.6897 | -99.6917
Mexico City | 19.6897 | -74.6917
Sao Paulo | -23.6897 | -46.6917