一、序列
在Python中,最基本的数据结构是序列(sequence)。序列中的每个元素被分配一个序号——即元素的位置,也称为索引。第一个索引是 0,第二个则是 1,以此类推。序列中的最后一个元素标记为 -1,倒数第二个元素为 -2,以次类推。
所有序列类型都可以进行某些特定的操作。这些操作包括:索引(indexing)、分片(sliceing)、加(adding)、乘(multiplying)以及检查某个元素是否属于序列的成员(成员资格)。除此之外,Python还有计算序列长度、找出最大元素和最小元素的内建函数等等。另外所有序列都支持迭代,统称为可迭代对象(iterable)。
Python包含多种内建的序列,本文主要说列表、元组和字符串。其中字符和元组属于不可变序列,不能更改,代码更安全;列表则支持插入,删除和替换等元素,是一种可变的序列。
二、通用序列操作(字符串、列表、元组)
- 索引运算:key[index]
Python索引是从0开始的,当索引超出了范围时,Python会报一个IndexError错误。所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) – 1,如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。切片操作对列表来说是使用非常广泛的,灵活性特别大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 字符串; >>> string = "www.ywnds.com" >>> print(string[0]) w # 列表; >>> list = ["eric","andy","mark"] >>> print(list[0]) eric # 列表更改元素; >>> list[0] = "dkey" >>> print(list) ['dkey', 'andy', 'mark'] |
- 切片运算:key[index_start:index_end]
切片之后创建的是一个新的内存对象
1 2 3 4 5 6 7 8 |
>>> print(string[1]) w >>> print(string[1:]) ww.ywnds.com >>> print(string[0:3]) www >>> print(string[-3:]) com |
- 扩展切片运算:key[index_start:index_end:stride]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 字符串; >>> print(string[4:13]) ywnds.com >>> print(string[4:13:2]) ynscm >>> print(string[-1::-1]) moc.sdnwy.www # 列表; >>> list = range(10) >>> print(list[::-1]) [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] >>> print(list[0:len(list):2]) [0, 2, 4, 6, 8] >>> print(list[1::2]) [1, 3, 5, 7, 9] >>> print(list[1::2][::-1]) [9, 7, 5, 3, 1] |
- 相关函数(按照其在ASCII码中的顺序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
>>> list = [1,2,3] # min()获取最小值; >>> print(min(list)) 1 # max()获取最大值; >>> print(max(list)) 3 # sum()获取总和; >>> print(sum(list)) 6 # len()获取元素长度; >>> print(len(list)) 3 # del()删除对象引用(可根据索引删除); >>> del(list[0]) >>> print list [2, 3] >>> del(list[0:3]) >>> print list [] # 拼接; >>> list1 = [1,2,3] >>> list2 = ['andy','eric'] >>> print(list1 + list2) [1, 2, 3, 'andy', 'eric'] # 重复; >>> list1 = [1,2,3] >>> print(list1 * 2) [1, 2, 3, 1, 2, 3] |
- 成员关系判断:OBJ in NAME
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> list = [1,2,3] >>> 2 in list True >>> 10 in list False >>> 2 not in list False >>> 10 not in list True |
三、字符串(string)
字符串是Python中最常用的数据类型之一,使用单引号或双引号来创建字符串,使用三引号创建多行字符串。字符串要么使用两个单引号,要么两个双引号,不能一单一双!Python不支持单字符类型,单字符在Python中也是作为一个字符串使用。
字符串是不可变的序列数据类型,不能直接修改字符串本身,和数字类型一样!
Python 2字符串不支持Unicode编码,其大小为8bit,要想支持Unicode编码,需使用方法u”content”。而在Python 3全面支持Unicode编码,所有的字符串都是Unicode字符串,不在需要使用u,可以自动识别,其大小为16bit。所以传统Python 2存在的编码问题不再困扰我们,可以放心大胆的使用中文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 字符串赋值; >>> string1 = 'hello' >>> string2 = 'world' # 字符串拼接; >>> print(string1 + string2) helloworld # 字符串复制; >>> print(string1 * 2) hellohello >>> print('#'*20) #################### # 字符串格式化; >>> print("%s" % "hello") hello >>> '{name},{alias}'.format(name='dkey',alias='ywnds') 'dkey,ywnds'; # 支持Unicode编码; >>> string = "中文" >>> print(string) 中文 |
特别注意,Python 的字符串是不可变的(immutable)。因此,用下面的操作,来改变一个字符串内部的字符是错误的。不允许的。
1 2 3 4 5 |
>>> s = 'hello' >>> s[0] = 'H' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment |
Python 中字符串的改变,通常只能通过创建新的字符串来完成。比如上述例子中,想把 ‘hello’ 的第一个字符 ‘h’,改为大写的 ‘H’,我们可以采用下面的做法:
1 2 |
>>> s = 'H' + s[1:] >>> s = s.replace('h','H') |
第一种方法,是直接用大写的 ‘H’,通过加号操作符,与原字符串切片操作的子字符串拼接而成新的字符串。
第二种方法,是直接扫描原字符串,把小写的 ‘h’ 替换成大写的 ‘H’,得到新的字符串。
由此,可以看出想改变字符串必须得老老实实创建新的字符串。因此,每次想要改变字符串,往往需要 O(n) 的时间复杂度。其中,n 为新字符串的长度。但随着 Python 版本的更新,Python 也越来越聪明了,性能优化得越来越好了。
这里,着重说一下,使用加法操作符 ‘+=’ 的字符串拼接方法。因为它是一个例外,打破了字符串不可变的特性。操作方法如下所示:
1 |
str1 += str2 // 表示 str1 = str1 + str2 |
来看下面的例子:
1 2 3 |
s = '' for n in range(0, 100000): s += str(n) |
这个例子,每次循环,似乎都得创建一个新的字符串;而每次创建一个新的字符串,都需要 O(n) 的时间复杂度。因此,总的时间复杂度为 O(1) + O(2) + … + O(n) = O(n^2)。这样到底对不对呢?在 Python2.5 之前,确实是这样的。但自从 Python 2.5 开始,每次处理字符串的拼接操作时,Python 首先会检测 str1 还有没有其他的引用。如果没有的话,就会尝试原地扩充字符串 buffer 的大小,而不是重新分配一块内存来创建新的字符串并拷贝。这样的话,上述例子中的时间复杂度就仅为 O(n) 了。
因此,以后在写程序遇到字符串拼接时,如果使用 ‘+=’ 更方便,就放心地去用吧,不用过分担心效率问题了。
字符常用的内置方法
str.upper()
将一个字符串转变为大写。
1 2 3 |
>>> string = 'hello' >>> string.upper() 'HELLO' |
str.lower()
将一个字符串转变为小写。
1 2 3 |
>>> string = 'Hello' >>> string.lower() 'hello' |
str.capitalize()
将一个字符串首字母转换我大写。
1 2 3 |
>>> string = 'hello' >>> string.capitalize() 'Hello' |
str.strip([chars])
返回去除两侧(不包括内部)指定字符串,默认是去除空格;另外还有rstrip和lstrip,分别是删除右边和左边指定字符。
1 2 3 4 5 6 7 |
>>> string = ' hello ' >>> string.strip() 'he llo' >>> string = 'hello' >>> string.strip('o') 'hell' |
str.index(sub[, start[, end]])
找到指定字符串首次出现的位置,[, start[, end]]表示从哪里开始和结束,可省略。
1 2 3 |
>>> string = 'hello' >>> string.index('l') 2 |
str.replace(old, new[, count])
替换一个字符或一个字符串,其中old表示修改前内容,new表示修改后内容,count表示要修改几个,可省略。
1 2 3 4 5 6 7 8 |
# 替换字符; >>> string = 'hello' >>> string.replace('l','L',2) 'heLLo' # 删除字符; >>> string.replace('e','') 'hllo' |
str.split(sep=None, maxsplit=-1)
用来将字符串分割成序列,可以执行最大分割多少次。
1 2 3 |
>>> string = "www.ywnds.com" >>> string.split('.') ['www', 'ywnds', 'com'] |
配置索引运算,可以显示执行的元素:
1 2 |
>>> string.split('.')[0] 'www' |
str.startswith(suffix[, start[, end]])
判断对象中是否为执行字符首部,是则为真,否则为假。
1 2 3 4 5 |
>>> string = 'hello' >>> string.startswith('h') True >>> string.startswith('o') False |
str.endswith(suffix[, start[, end]])
判断对象中是否为执行字符结尾,是则为真,否则为假。
1 2 3 4 5 |
>>> string = 'hello' >>> string.endswith('l') False >>> string.endswith('o') True |
str.join(iterable)
使用’某某’作为分隔符连接序列中的字符。
1 2 3 4 5 |
>>> list = ['w','w','w','.','y','w','n','d','s','.','o','r','g'] >>> ''.join(list) 'www.ywnds.org' >>> '.'.join(list) 'w.w.w...y.w.n.d.s...o.r.g' |
str.find(sub[, start[, end]])
可以在一个较长的字符串中查找子串,它返回子串所在位置的最左端索引,如果没有找到则返回-1。
1 2 3 4 5 6 7 |
>>> string = "www.ywnds.com" >>> string.find('s') 8 >>> string.find('ywnds') 4 >>> string.find('ywd') -1 |
str.translate(table)
这个方法和replace方法一样,可以替换字符串中的某些部分,但是和前者不同的是,translate方法只处理单个字符。它的优势在于可以同时进行多个替换,有些时候比replace效率高的多。
str.format()
格式化输出字符串。简单的说就是format里面的东西去替换前面的内容,在替换的时候,可以按某种规定来输出;format方法在Python2.6之后引入,替代了原先的%,显得更加优雅。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
>>> name = 'dkey' >>> name.format() 'dkey' # 通过位置; >>> '{0},{1}'.format('dkey',20) 'dkey,20' >>> '{},{}'.format('dkey',20) 'dkey,20' >>> '{0},{1},{0}'.format('dkey',20) 'dkey,20,dkey' # 通过关键字; >>> '{name},{age}'.format(age=18,name='dkey') 'dkey,18' # 通过映射LIST; >>> list = ['dkey',20,'china'] >>> print('my name is {0[0]},from {0[2]},age is {0[1]}'.format(list)) my name is dkey,from china,age is 20 # 用来做金额的千位分隔符; >>> '{:,}'.format(1234567890) '1,234,567,890' |
str.isalnum()
是否全为字母或数字。
str.isalpha()
是否为全字母。
str.isdigit()
是否为全数字。
str.islower()
是否为全小写。
str.isupper()
是否为全大写。
str.isspace()
是否为空格。
str.isdecimal()
是否为小数。
还有很多方法,如: center()、decode()、encode()、rindex()、rsplit()等。
四、列表(list)
列表是 Python 中最基本也是最常用的数据结构之一。列表中的每个元素都被分配一个数字作为索引,用来表示该元素在列表内所排在的位置。第一个元素的索引是0,第二个索引是1,依此类推。实际上,列表和元组,都是一个可以放置任意数据类型的有序集合。在绝大多数编程语言中,集合的数据类型必须一致。不过,对于 Python 的列表和元组来说,并无此要求。
Python的列表属于容器类型,是一个有序可重复的元素集合,可嵌套、迭代、修改、分片、追加、删除,成员判断等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 初始化一个空列表; >>> list = [] # 各种列表赋值; >>> list1 = ['eric','andy'] >>> list2 = ['eric',123,True] >>> list3 = ['eric',['andy','mark']] # 复制一个列表元素(内存对象可变); >>> list4 = list3[:] >>> print list4 ['eric', ['andy', 'mark']] # 根据索引支持原处修改元素(元素不存在则抛异常); >>> list = [1,2,3] >>> list[0] = 10 >>> print list [10, 2, 3] >>> list[2] = 'know' >>> print list [10, 2, 'know'] |
内置函数del()可删除元素(删除元素/删除切片/删除扩展切片)
1 2 3 4 5 6 7 |
>>> list = [1,2,3] >>> del(list[1]) >>> print list [1, 3] >>> del(list[0:3]) >>> print list [] |
列表常用的内置方法
L.append(object)
追加元素到末尾,一次只能追加一个元素。
1 2 3 4 5 6 7 8 9 10 11 |
>>> list1 = [1,2,3] >>> list2 = ['andy','eric'] >>> list1.append(10) >>> print list1 [1, 2, 3, 10] >>> list2.append('dkey') >>> print list2 ['andy', 'eric', 'dkey'] >>> list1.append(list2) >>> print list1 [1, 2, 3, 10, ['andy', 'eric', 'dkey']] |
L.insert(index, object)
指定索引位置添加元素。
1 2 3 4 |
>>> list = [1,2,3] >>> list.insert(0,10) >>> print list [10, 1, 2, 3] |
L.extend(iterable)
合并列表元素,跟append方法作用是不同的,可以对比一下代码。
1 2 3 4 5 6 7 8 |
>>> list1 = ["andy","eric"] >>> list2 = ["dkey","mark"] >>> list1.extend(list2) >>> print list1 ['andy', 'eric', 'dkey', 'mark'] >>> list1.extend("jerry") >>> print list1 ['andy', 'eric', 'dkey', 'mark', 'j', 'e', 'r', 'r', 'y'] |
L.count(value)
查询指定元素出现的次数。
1 2 3 |
>>> list = [1,2,3,1,2,3] >>> list.count(3) 2 |
L.index(value, [start, [stop]])
查看列表中指定字符出现的索引位置。
1 2 3 4 5 |
>>> list = [1,10,3,10,'a','b'] >>> list.index(10) 1 >>> list.index(10,3) 3 |
把索引使用变量替代可以做更加方便的原处修改
1 2 3 |
>>> list[list.index('a')] = 'c' >>> print list [1, 10, 3, 10, 'c', 'b'] |
L.pop([index])
根据索引从列表中剔除一个元素,不给索引默认剔除最右边的一个元素,超出索引范围会抛出异常。
1 2 3 4 5 6 7 8 9 |
>>> list = [1,2,3] >>> list.pop(1) 2 >>> print list [1, 3] >>> list.pop() 3 >>> print list [1] |
L.remove(value)
根据对象删除一个元素。
1 2 3 4 |
>>> list = [1,2,3] >>> list.remove(3) >>> print list [1, 2] |
L.sort(key=None, reverse=False)
根据元素排序,同样是在列表原处修改,支持升序降序。
1 2 3 4 |
>>> list = [1,3,2] >>> list.sort() >>> print list [1, 2, 3] |
L.reverse()
根据元素反转,同样是在列表原处修改。
1 2 3 4 |
>>> list = [1,3,2] >>> list.reverse() >>> print list [2, 3, 1] |
PS:Python 3针对list增加了clear、copy方法。
五、元组(tuple)
元组跟列表非常类似,属于容器类型,是任意对象的有序集合。但是tuple一旦初始化就不能修改(immutable),它属于不可变对象。
1 |
>>> classmates = ('Michael', 'Bob', 'Tracy') |
现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
不可变的tuple有什么意义?因为tuple不可变,所以性能更优(指定全部元素为immutable的tuple会作为常量在编译时确定,所以速度更快),多线程安全,不需要锁,不担心被恶意修改或者不小心修改。如果可能,能用tuple代替list就尽量用tuple。
另外就是元组可哈希(字符也可哈希),列表不可哈希(字典和集合也不可哈希),什么意思呢?简单理解就是支持hash()函数的称之为可哈希,反之称之为不可哈希。
1 2 3 4 5 6 7 8 9 10 11 |
# 可哈希; >>> tuple = (1,2,3) >>> hash(tuple) 2528502973977326415 # 不可哈希; >>> list = [1,2,3] >>> hash(list) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' |
由于tuple是immutable对象,可哈希,所以可以当做dict的key;而list不可哈希,自然也不能当做dict的key。如下测试。
1 2 3 4 5 6 |
>>> user_info_dict = {} >>> user_info_dict[('dkey', 23)] = 'shanghai' >>> user_info_dict[['dkey', 23]] = 'shanghai' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' |
在Python的内置数据类型中,基本上可变的数据类型都是不可哈希的,而不可变的数据类型是可哈希的。
元组的初始化操作:
1 2 3 4 5 6 7 |
# 初始化元组,当只有一个元素时必须以逗号结尾; >>> tuple = ('eric',) >>> tuple = (1,2) # 元组嵌套列表; >>> tuple1 = (1,2,3) >>> tuple2 = (1,2,3,[1,2,3]) |
元组嵌套列表,虽然元组本身不可变,但是如果元组内嵌套了可变类型的元素,那么此类元素的修改不会返回新元素而是在原处修改。
1 2 3 4 |
>>> tuple2[3][0] = 10 >>> tuple2[3].append(20) >>> print(tuple2) (1, 2, 3, [10, 2, 3, 20]) |
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的。理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。
元组常用的内置方法
由于元组不可变,所以相对应的增删改方法都没有,只有count和index方法。
T.count(value)
统计元素出现的次数。
T.index(value, [start, [stop]])
显示元素的索引位置。
六、说说不可变对象
上面我们讲了,str和tuple是不可变对象,而list是可变对象。在Python的内置数据类型中,基本上可变的数据类型都是不可哈希的,而不可变的数据类型是可哈希的。对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
1 2 3 4 |
>>> a = [2,3,1] >>> a.sort() >>> a [1, 2, 3] |
而对于不可变对象,比如str,对str进行操作呢:
1 2 3 4 5 |
>>> a = 'abc' >>> a.replace('a','A') 'Abc' >>> a 'abc' |
使用字符串有个replace()方法,也确实变出了’Abc’,但变量a最后仍是’abc’,这就是不可变对象,应该怎么理解呢? 我们a.replace(‘a’,’A’)结果赋值给一个变量:
1 2 3 4 5 6 |
>>> a = 'abc' >>> b = a.replace('a', 'A') >>> b 'Abc' >>> a 'abc' |
要始终牢记的是,a是变量,而’abc’才是字符串对象!
当我们调用a.replace(‘a’, ‘A’)时,实际上调用方法replace是作用在字符串对象’abc’上的,而这个方法虽然名字叫replace,但却没有改变字符串’abc’的内容。相反,replace方法创建了一个新字符串’Abc’并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串’abc’,但变量b却指向新字符串’Abc’了。
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
为了更好的说明问题,我们来看一个小实例,再来理解一下什么是可变和不可变对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
a = 1 """ 例子1:问 a 的值最终是多少? """ def fun_a(a): print(f'函数中形参a的id:{id(a)}') a = 2 print(f'a=2之后a的id:{id(a)}') print(f'数字2的id:{id(2)}') fun_a(a) print(f'函数外面a的id:{id(a)}') print(f'a的值:{a}') |
执行程序,结果如下:
1 2 3 4 5 |
函数中形参a的id:4346840400 a=2之后a的id:4346840432 数字2的id:4346840432 函数外面a的id:4346840400 a的值:1 |
str为不可变对象,当一个引用a传递给函数fun_a的时候,函数会自动复制一份引用a(可以参考打印输出的id内存地址),这个函数里的引用a和外边的引用a没有半毛关系,内存地址就不一样!也就是说函数fun_a把引用指向了一个不可变对象nums、str,所以不会影响到函数外面的同名变量。
1 2 3 4 5 6 7 8 9 10 11 |
b = [1] """ 例子2:b是一个列表,问,b的值是多少? """ def fun_b(b): print(f'函数中形参b的id:{id(b)}') b.append(2) print(f'b.append之后b的id:{id(b)}') fun_b(b) print(f'函数外面b的id:{id(b)}') print(f'b的值:{b}') |
执行程序,结果如下:
1 2 3 4 |
函数中形参b的id:4546159176 b.append之后b的id:4546159176 函数外面b的id:4546159176 b的值:[1, 2] |
list为可变对象,函数内的引用指向的是可变对象list,对它的操作就和定位了指针地址一样,在内存里进行修改。可以看到对list进行append操作内存地址无论是在函数内还是函数外都是不变的!
七、列表与元祖的存储方式
前面说了,列表和元祖最重要的区别就是,列表是动态的,可变的;而元祖是静态的,不可变的。这样的差异,势必会影响两者的存储方式。来看下面的例子:
1 2 3 4 5 6 |
>>> l = [1, 2, 3] >>> l.__sizeof__() 64 >>> t = (1, 2, 3) >>> t.__sizeof__() 48 |
可以看到,对列表和元祖,我们放置了相同的元素,但是元祖的存储空间,却比列表要少 16 字节。这是为什么呢?
事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述列子中,对于 int 类型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外的空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
>>> l = [] >>> l.__sizeof__() // 空列表的存储空间为 40 字节 40 >>> l.append(1) >>> l.__sizeof__() // 加入元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4 72 >>> l.append(2) >>> l.__sizeof__() // 由于之前分配了空间,所以加入元素 2,列表空间不变 72 >>> l.append(3) >>> l.__sizeof__() // 同上 72 >>> l.append(4) >>> l.__sizeof__() // 同上 72 >>> l.append(5) >>> l.__sizeof__() // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间 104 |
上面的例子,大概描述了列表空间分配的过程。我们可以看到,为了减小每次增加/删除操作时空间分配的开销,Python 每次分配空间时都会额外多分配一些,这样的机制(over-allocating)保证了其操作的高效:增加/删除的时间复杂度均为 O(1)。
但是对于元祖,情况就不同了。元祖长度大小固定,元素不可变,所以存储空间固定。
八、列表和元祖的性能
通过上面列表和元祖存储方式的差异,我们可以得出结论:元祖要比列表更加轻量级一些,所以总体上来说,元祖的性能速度要略优于列表。
另外,Python 在后台,对静态数据做一些资源缓存。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是对于一些静态变量,比如元祖,如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元祖时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
下面的例子,是计算初始化一个相同元素的列表和元祖分别所需的时间。我们可以看到,元祖的初始化速度,要比列表快 5 倍。
1 2 3 4 |
>>> timeit.timeit('x=(1,2,3,4,5,6)') 0.024178152001695707 >>> timeit.timeit('x=[1,2,3,4,5,6]') 0.09497855699737556 |
但如果是索引操作的话,两者的速度差别非常小,几乎可以忽略不计。
当然,如果你想要增加、删除或者改变元素,那么列表显然更优。原因你现在肯定知道了,那就是对于元祖,你必须得通过新建一个元祖来完成。
Tips:列表和元祖的内部实现都是 array 的形式,列表因为可变,所以是一个 over-allocate 的 array,元祖因为不可变,所以长度大小固定。
<延伸>