作者: Jim Wang 公众号: 巴博萨船长

Python 标准数据类型

Python 定义了一些标准数据类型,用于存储各种类型的数据。Python有五个标准的数据类型,分别是:

  • Numbers(数字)
  • String(字符串)
  • List(列表)
  • Tuple(元组)
  • Dictionary(字典)

Python 变量申明自由,使用灵活,每种数据类型都有存在的原因和使用的技巧。今天的文章我们就介绍一下元组的基本知识,介绍一下它的使用技巧,讨论一下它存在的原因。

img

Python 数据类型之元组

前一篇文章提到的五种标准变量类型中string, list, tuple 都属于序列(sequence)类型。Python内置共有6种序列类型变量,分别为:

str, unicode, list, tuple, buffer, xrange(range)

除一种可变序列类型,即List列表,其余的如tuple和range都属于不可变的序列类型。这里所谓的”不可变” (immutable)是说,对象创建之后,就不可以通过一些操作改变其内部状态,简言之就是对象本身不可改变。

img

Python 元组赋值与快速赋值

Python 元组使用圆括号“()”,作为与其他数据类型区别的标志, 元素之间用逗号“,”分隔。元组在创建时元素的数据类型不限,可以是各种数据类型的集合。与列表相似,元组中的每个元素都分配一个数字作为索引,第一个索引值为0, 第二个为1,以此类推。元组列表普通赋值定义方法如下:

1
2
3
tup1 = ('physics', 'chemistry', 1997, 2000)
tup2 = (1, 2, 3, 4, 5 )
tup3 = ("a", "b", "c", "d")

初始化完成之后就可以使用元组。元组和上一篇关于列表的文章相似。如果元组初始化长度过长,就可以使用下列方法对元组赋值:

1
2
3
4
5
6
7
8
9
10
11
>>> tup1 = (0,) * 5 # 数字类型
>>> tup1(0, 0, 0, 0, 0)
>>> tup2 = ("",) * 8 # 字符串类型
>>> tup2
('', '', '', '', '', '', '', '')
>>> tup3 = (None,) * 9 # 空
>>> tup3
(None, None, None, None, None, None, None, None, None)
>>> tup4 = (True, )*7 # 布尔类型
>>> tup4
(True, True, True, True, True, True, True)

此方法快速高效,可以对指定长度的组元进行快速初始化,弊端在于不能在指定元素的类型,但可以按顺序混合元素的数据类型,如下:

1
2
3
>>> tup5 = (True, "")* 3
>>> tup5
(True, '', True, '', True, '')

在这里要指出两点,一、此种快速赋值初始化元组理论上可行,但是不具有任何使用意义。理论上不存在,需要初始化一个元素相同的元组的情况。二、与列表不同,在初始化元组的候,如果元组中只有一个元素,那么需要在该元素后面添加逗号来消除歧义,如果没有,Python解释器会视为申明一个普通变量而非元组类型。

Python 的列表索引,切片与排序

Python 的元组索引方式和列表的索引方式相似:使用元组的变量名加方括号与索引值进行索引。Python的独特优势:在方括号中使用冒号“”+“数字” 切片方式同样适用于元组。切片可以理解为:取元组中指定的连续元素,返回值同为元组。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> tup1 = ('Google', 'Runoob', 'Taobao')
>>> tup1[2] # 取第二个
'Taobao'
>>> tup1[-1] # 取倒数第一个
'Taobao'
>>> tup1[1:] # 取 从索引1至最后一个
('Runoob', 'Taobao')
>>> tup1[:2] # 取 从开始至索引2
('Google', 'Runoob')
>>> tup1[-1:] # 取倒数第一个至倒数第一个
('Taobao',)
注意:上述示例中,tup1[-1]意为取最后一个,得到的结果为一个字符串类型,
tup1[-1:]也同为取最后一个,但因为指定了切片范围,则返回值为一个单一元素的元
组。在编程时应注意这点,避免因数据类型问题写出很多不必要的代码。

如同列表一样,元组在Python也是一个类。意味着元组有自己的类方法。可以通过类方法对元组进行操作。常用类方法如下:

1
2
1    tup.count(obj) # 某个元素在列表中出现的次数
2 tup.index(obj) # 从列表中找出某个值第一个匹配项的索引位置

感兴趣的可以对比一下上一篇关于列表的文章,对比一下两者类方法相同与不同之处。简言之:元组不具有任何能改变自己的类方法

img

接下来我给大家介绍一下元组倒序排列(revers)和整理排序(sort)的实战经验。整理排序时,默认情况下,数字元组排序规则为:由小到大;字符串元组排列规则:由“0”到“10”,然后由a到z;数字和字符串混合型元组排序规则为:先数字,后字符。请参考下方例子:

1
2
3
4
5
6
>>> tup = ('a', 1, "c", 3, "b", 2)
>>> sorted(tup) # 使用Python内置方法
[1, 2, 3, 'a', 'b', 'c']
注意,sorted()为Python编程语言的
内置函数,元组不具有类似列表的类
方法sort()用于排序。

仔细观察上述示例,使用Python内置函数sorted(),返回值为一个列表,这与变量tup数据类型无关,仅与该函数的返回值类型有关。在元组倒序排列的问题,请参考下方例子,可以通过一种特殊的切片方法直接生成出一个倒序排列的新的元组。

1
2
3
4
5
6
7
>>> tup = ('a', 1, "c", 3, "b", 2)
>>> tup[::-1] # 采用切片法
(2, 'b', 3, 'c', 1, 'a')
>>> lt
['a', 1, 'c', 3, 'b', 2]
>>> reversed(tup)
<reversed object at 0x00000000002AEED68>

虽然python也有一个用于倒序排列的内置函数reversed() 也可以用于元组。此函数返回值为一个元组型的迭(die)代器实例,而非一个新的元组,所以说用类似“**[::-1]**”切片方式才是元组倒序排列的最佳选择。

Python编程语言一些内置函数,比如:元组间比较cmp(),获取长度len(),最大值max()和最小值min(),转变为列表list(seq)等,这些函数不仅适用于列表,也同样适用于元组,毕竟两者都属于序列类型。如上篇文章所介绍的,用于检测是否为非空的函数all(),和是否有任一个元素为的函数any也能用于元组。

列表与元组

如果大家开始学习Python,学到这里的时候,脑子里都应该有这样一个问题,Python中有列表这种数据类型,为什么还需要一个元组。接下来我们来探讨一下其存在的原因。

列表和元组两者是相似的数据类型,都是作为一个容器,用于存储数据。本质上都属于序列类型,但列表可变,元组不可变。除了关注”变”以外,理论上我们还需要注意他们在语法上的应用区别,元组应当作为异构的数据集合(元素数据类型不同),类似于C语言里面的struct 结构体;列表应当作同构的数据集合(元素数据类型相同),类似与C语言中的array数组。两者在Python中的使用原则应为,元组关注结构,列表关注排列。但在实际应用中,因为Python的列表并不限制数据类型,所以这一原则并没有被严格遵守。

元组使用场景通常为:同时为多个变量名赋值;一次性为函数传递所有参数和调用函数时一次性返回不同的类型的数据,如下:

  • 场景一,为多个变量名同时赋值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a, b, c = (1, 2, 3) # case 1: 使用元组
>>> a, b, c
(1, 2, 3)
>>> a, b, c = [1, 2, 3] # case 2: 使用列表
>>> a, b, c
(1, 2, 3)
>>> a, b, c = 1, 2, 3 # case 3: 此种方法也视为 元组赋值
>>> a, b, c
(1, 2, 3)
>>> d = (1, 2, 3)
>>> a, b, c = d # case 4: 此种方法也视为 元组赋值
>>> a, b, c
(1, 2, 3)
>>> d = [1, 2, 3]
>>> a, b, c = d # case 5: 此种方法也视为 元组赋值
>>> a, b, c
(1, 2, 3)

虽然上述场景中,case1与case2, case4与case5能达到相同的目的但是本质上来讲case2和case5都是由Python解释权将列表转换为元组,然后再给a, b, c三个变量赋值的。case3比较特殊,虽然没有使用圆括号,但是Python的解释权会自动讲”1, 2, 3“ 转换为元组,然后进行赋值。

  • 场景二,一次性为函数传递所有参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> def fun1(a="", b=None, c=False):
... print "a : ",
... print a,
... print " b : ",
... print b,
... print " c : ",
... print c
...
>>> d = (1, 2, 3) # 使用元组
>>> fun1(d) # 错误方式
a : (1, 2, 3) b : None c : False
>>> fun1(*d) # 正确方式a : 1 b : 2 c : 3
>>> d = [1, 2, 3] # 使用列表
>>> fun1(d) # 错误方式
a : [1, 2, 3] b : None c : False
>>> fun1(*d) # 正确方式
a : 1 b : 2 c : 3

同样的虽然上述场景中,使用列表和元组通过正确地方式都能够一次性地传递多个参数给函数fun1()。 但是与场景一相同,都是由Python解释权将列表转换为元组,然后再给a, b, c三个参数赋值的。

场景三,调用函数时一次性返回不同的类型的数据:

1
2
3
4
5
>>> def fun2():
... return "HalloWorld", 1, False
...
>>> fun2()
('HalloWorld', 1, False)

上述场景中的问题,只能通过元组来实现(圆括号有与无,都为元组)。虽然也可以定义一个列表,将所有返回值打包然后返回一个列表,但是会产生许多不必要的代码,还需要索引使用返回值,过于繁琐。

1
2
3
4
5
6
7
8
9
>>> for x in (1, 2, 3): print x
...
123
>>> for x in [1, 2, 3]: print x
...
123
>>> for x in "123": print x
...
123

在做上述相同工作的时候,列表和远足可以得到相似的结果,但是效率上呢?我们做些测试,如下:

测试一:

1
2
3
4
$ python -mtimeit "['fee', 'fie', 'fo', 'fum']"
10000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit "('fee', 'fie', 'fo', 'fum')"
10000000 loops, best of 3: 0.0563 usec per loop

测试二:

1
2
3
4
a = tuple(range(1000))
b = list(range(1000))
a.__sizeof__() # 8024
b.__sizeof__() # 9088

第一个测试中,我们分别创建元素相同的列表和元组。然后测试Python解释器创建它们所需时间,进而得出最优值。我们可以发现元组的创建时间是列表创建时间的八分之一。第二个测试中,我们尝试初始化相同长度的元组和列表,然后比较两个所需内存大小,结果现实元组相比列表所需内存相对较少。总而言之:元组的性能优于列表。在需要关注性能与内存使用率的时候,元组还是最优选择。

但如果作为仅作为序列型的数据,忽略两者性能上的差异,列表可以完全替代元组解决程序中遇到的问题。这样的话,是不是在Python中,元组就没有专属的应用场景了呢?是不是就不存在非元组不可的情况呢?当然不是

列表作为可变序列,是不可以成为字典(dictionary,Python的一种数据类型)的键值(即key)的,而元组为不可变序列则可以作为键值。如下:

1
2
3
4
a    = (1,2)
b = [1,2]
c = {a: 1} # 合法
c = {b: 1} # 非法

该种情况的使用场景为:如果需要将经纬度当多一个字典的键值时;如果需要将行列值作为一个字典的键值是;如果需要讲屏幕位置作为一个字典的键值等。


版权声明:
文章首发于 Jim Wang's blog , 转载文章请务必以超链接形式标明文章出处,作者信息及本版权声明。