“Life is short, You need Python” – Bruce Eckel
Environment
Python 中的数据类型可分为基本数据类型和组合数据类型,基本数据类型包括整型、浮点型、复数类型、字符串、布尔类型,组合数据类型包括列表、元组、字典、集合,其中还有更细的分类,如整型包括不同进制,字典包括有序和无序等。本文记录了组合数据类型的常用操作、解析式以及一些底层实现的内容。
ls = [[True, False], 1, {'type': 'list', 'name': 'hello'}]
empty_list = [] # an empty list
list(iterable=())
,传入可迭代对象list('string') # ['s', 't', 'r', 'i', 'n', 'g']
list(range(5)) # [0, 1, 2, 3, 4]
ls = [i for i in range(5)]
print(ls) # [0, 1, 2, 3, 4]
ls = [9, 5, 2, ['legal', 'healthy'], 7]
print(ls[0]) # 9
print(ls[-2]) # ['legal', 'healthy']
print(ls[3][0]) # legal
# 通过索引修改元素
ls[3] = 0
print(ls) # [9, 5, 2, 0, 7]
ls = [9, 5, 2, ['legal', 'healthy'], 7]
print(ls[1:3]) # 表示从ind=1 开始到ind=2,不包括ind=3
# [5, 2]
print(ls[:3]) # 从头开始到索引 2
# [9, 5, 2]
print(ls[3:]) # 从索引 3 开始到最后
# [['legal', 'healthy'], 7]
print(ls[:]) # 得到整个列表,是常用的列表拷贝方法
# [9, 5, 2, ['legal', 'healthy'], 7]
print(ls[1::2]) # 步长为 2
# [5, ['legal', 'healthy']]
print(ls[-1:-4:-2]) # 反向索引
# [7, 2]
print(ls[::-1]) # 得到反序排列的新列表对象
# [7, ['legal', 'healthy'], 2, 5, 9]
list.append(object)
在列表末尾加上一个元素ls = [9, 5, 2, ['legal', 'healthy']]
ls.append(7)
print(ls) # [9, 5, 2, ['legal', 'healthy'], 7]
list.extend(iterable)
在列表末尾加上多个元素ls = [9, 5, 2, ['legal', 'healthy'], 7]
ls.extend(['a', 'b', 3])
print([9, 5, 2, ['legal', 'healthy'], 7, 'a', 'b', 3])
list.insert(index, object)
向列表指定位置插入元素,从该位置开始的元素向后移一个位置ls = [9, 5, 2, ['legal', 'healthy'], 7]
ls.insert(3, 0)
print(ls) # [9, 5, 2, 0, ['legal', 'healthy'], 7]
list.pop(index=-1)
弹出指定索引的元素ls = [9, 5, 2, ['legal', 'healthy'], 7]
ls.pop(-2) # ['legal', 'healthy']
print(ls) # [9, 5, 2, 7]
list.remove(value)
从列表中删除指定元素,传入需要删除的元素。对于重复元素,调用一次只删除索引最小的那一个。好又快地需要删除所有重复元素,可以使用迭代结构结合反向索引,参考Python基础(四)控制结构中for迭代的例子。ls = [1, 1, 1, 1, 0, 0, 1]
ls.remove(1)
print(ls)
# [1, 1, 1, 0, 0, 1]
list.clear()
清空列表中的元素,使原列表对象称为空列表ls = [9, 5, 2, ['legal', 'healthy'], 7]
ls.clear()
print(ls)
+
,连接两个列表,效果等价于 list.extend(iterable)
方法,但拼接返回连接后产生的新列表对象。总是首选使用 extend
方法而不是拼接 +
。ls = [9, 5, 2] + [['legal', 'healthy'], 7]
print(ls) # [9, 5, 2, ['legal', 'healthy'], 7]
*
重复ls = [1, 2, 'a'] * 3
print(ls) # [1, 2, 'a', 1, 2, 'a', 1, 2, 'a']
注意,使用 *
创建多维列表可能出现 bug!
例 使用 *
创建一个 4 x 3 的二维列表将失败
ls = [[0] * 3] * 4
ls
# [[0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]]
# 改变 0, 0 为止的元素,却造成所有第一行元素的改变
ls[0][0] = 1
ls
# [1, 0, 0]
# [1, 0, 0]
# [1, 0, 0]
# [1, 0, 0]
原因是 ls * n
是将 n 份列表对象的浅拷贝连接(深浅拷贝问题参考Python基础(十) Part 1)。解决方法为,改用列表解析式(本文 Part 5.1)。
list1 = [1, 2, 3]
list2 = [4, 1, 3]
print(list1 > list2) # False
list3 = ['a', 'b', 'c']
print(list1 > list3) # 报错 TypeError
in
ls = [123, ['xyz', 666], 456]
print(123 in ls) # True
print('xyz' in my_list) # False,因只能判断一级对象
print('xyz' not in my_list) # True
print('xyz' in my_list[-2]) # True
list.count(value)
返回列表中某一元素值出现的次数ls = [123, 456] * 6
print(ls.count(123)) # 6
list.index(value, start=0, stop=9223372036854775807)
返回待查找元素值在列表中第一次出现位置的索引,注意查找范围不包括 stop 索引ls = [123, 456, 789, 123, 'abc', 123]
print(ls.index(123)) # 0
print(ls.index(123, -2)) # 5
print(ls.index(123, 1, 4)) # 3
print(ls.index(123, 1, 3) # 若没有该值的元素,则报错ValueError
list.reverse()
将列表对象倒序ls = [9, 5, 2, ['legal', 'healthy'], 7]
ls.reverse()
print(ls) # [7, ['legal', 'healthy'], 2, 5, 9]
list.sort(*, key=None, reverse=False)
将原列表对象按元素排序(默认为升序),支持仅包含 int 和(或) float 类型元素的列表,支持仅包含 int 和(或) float 类型元素的列表ls.sort(reverse=True)
print(ls) # [93, 78, 58.0, 23.0, 15]
list.copy()
,和切片复制等价,均为浅拷贝ls = [0, 1, [2, 3], 4]
ls_slide_copy = ls[:]
ls_copy = ls.copy()
print(id(ls), id(ls_slide_copy), id(ls_copy)) # 三者不同
# 为浅拷贝
ls[0], ls[2][0] = 666, 666
print(ls_slide_copy) # [0, 1, [666, 3], 4]
print(ls_copy) # [0, 1, [666, 3], 4]
tp = (9, 5, 2, ['legal', 'health'], 7)
print(tp) # (9, 5, 2, ['legal', 'health'], 7)
# a tuple with a single element
number = (6)
tp_with_single_elem = 6, # 圆括号可省略
print(type(number), type(tp_with_single_elem)) #
tuple(iterable=())
函数,传入可迭代对象tp = [9, 5, 2, ['legal', 'health'], 7]
print(tp) # (9, 5, 2, ['legal', 'health'], 7)
def foo(x):
return x * 2, x / 2, x ** 2 # 打包返回
res = foo(6)
print(type(res)) #
res1, res2, res3 = res # 解包赋值
print(res1, res2, res3) # 12, 3.0, 36
# 遍历解包赋值
for loss, aux in [('loss1', 'aux1'), ('loss2', 'aux2')]:
print(loss, aux)
# 虽然元组是不可变的,但是其中包含的列表中的元素是可变的
tp = (9, 5, 2, ['legal', 'healthy'], 7)
tp[-2][0] = 'illegal'
print(tp) # (9, 5, 2, ['illegal', 'healthy'], 7)
由于无法对元组添加、删除操作,因此元组对象没有像列表中的那些方法。但拼接、删除可以通过操作符实现,不过不是对原元组对象操作,而是返回结果的新对象。
+
tp = (9, 5) + (2, ['legal', 'healthy']) + (7,)
print(tp) # (9, 5, 2, ['legal', 'healthy'], 7)
*
重复print(6 * (6, 8)) # (6, 8, 6, 8, 6, 8, 6, 8, 6, 8, 6, 8)
print(6 * 6,) # 36, 这样无法实现重复
tuple.count(value)
。同列表tuple.index(value, start=0, stop=9223372036854775807)
。同列表new_tp = tp[:]
tp[-2][0] = 'illegal'
print(tp) # (9, 5, 2, ['illegal', 'healthy'], 7)
print(new_tp) # (9, 5, 2, ['illegal', 'healthy'], 7)
{(1, 2): 'one_two', 3: 'three'} # 可
{[1, 2]: 'one_two', 3: 'three'} # 报错 TypeError
# 当出现相同大小的整数和浮点数作为键,保留浮点数键,其对于的值以后传入的为准
dc = {(1, 2): 666, 3.0: 'hi', 3: 'legal', 'string': 'healthy'}
print(dc) # {(1, 2): 666, 3.0: 'legal', 'string': 'healthy'}
dict(self)
函数,传入一个以二元组为元素的元组dc = dict((((1, 2), 666), (3.0, 'hi'), (3, 'legal'), ('string', 'healthy')))
print(dc) # {(1, 2): 666, 3.0: 'legal', 'string': 'healthy'}
dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
print(dc[3]) # 'legal'
print(dc[0]) # 报错 KeyError
dict.get(key, default=None)
,若访问的键存在,则返回对于的值,若访问的键不存在,则向对象中添加相应的键,其对应的值为向 default 传入的值。不能以关键字参数形式传入# 统计一个字符串中字符出现的频率
subject = 'ophthalmol'
dc = {}
for s in subject:
dc[s] = dc.get(s, 0) + 1
print(dc) # {'o': 2, 'p': 1, 'h': 2, 't': 1, 'a': 1, 'l': 2, 'm': 1}
dict.setdefault(key, default=None)
,若访问的键存在,则返回对应的值;若访问的键不存在,则向对象中添加相应的键,其对应的值为向 default 传入的值,并和返回设定默认值。但不能以关键字参数形式传入dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
dc.setdefault('new_key', 'new_item')
print(dc) # {(1, 2): 666, 3: 'legal', 'string': 'healthy', 'new_key': 'new_item'}
dict.keys()
返回一个可迭代对象,为所有的键dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
print(dc.keys()) # dict_keys([(1, 2), 3, 'string'])
dict.values()
返回一个可迭代对象,内容为所有的值dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
print(dc.values()) # dict_values([666, 'legal', 'healthy'])
dict.items()
返回一个可迭代对象,为所有的键与值组成的二元组dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
print(dc.items()) # dict_items([((1, 2), 666), (3, 'legal'), ('string', 'healthy')])
dict[key] = value
直接设定一个不存在的键,并赋一个值。(当键存在时,修改原有的值)dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
dc[-1] = 'zero'
print(dc) # {(1, 2): 666, 3: 'legal', 'string': 'healthy', -1: 'zero'}
dict.update()
,将其他字典或以二元组为元素的可迭代序列连接到原字典对象中# 加入其他字典对象的内容
dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
other_dc = {0: 'zero', 1: 'one'}
dc.update(other_dc)
print(dc) # {(1, 2): 666, 3: 'legal', 'string': 'healthy', 0: 'zero', 1: 'one'}
# 加入以二元组为元素的可迭代序列
dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
other_dc = [(0, 'zero'), (1, 'one')]
dc.update(other_dc)
print(dc) # {(1, 2): 666, 3: 'legal', 'string': 'healthy', 0: 'zero', 1: 'one'}
dict.pop(k)
,传入需要弹出的键-值对中的键,弹出对应的键dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
pop_out = dc.pop(3)
print(pop_out) # legal
print(dc) # {(1, 2): 666, 'string': 'healthy'}
dict.popitem()
,随机弹出一个键值对,以元组形式返回dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
pop_out = dc.popitem()
print(pop_out) # ('string', 'healthy')
print(dc) # {(1, 2): 666, 3: 'legal'}
dict.clear()
,清空字典对象dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
dc.clear()
print(dc) # {}
dc = {(1, 2): 666, 3: 'legal', 'string': 'healthy'}
print((1, 2) in dc, 0 in dc) # True False
dict.copy()
,浅拷贝dc = {'number': 666, 0: 'zero', 'list': ['legal', 'healthy']}
new_dc = dc.copy()
dc['list'][0] = 'illegal'
print(new_dc) # {'number': 666, 0: 'zero', 'list': ['illegal', 'healthy']}
st = {(1, 2), 3, 'string'}
print(st) # {(1, 2), 3, 'string'}
# 注意,空集合无法直接创建
empty_st = {}
print(type(empty_st) # dict
set()
函数,空集合只能使用该函数创建# 传入可迭代对象
st = set(((1, 2), 3, 'string'))
print(st) # {(1, 2), 3, 'string'}
# 创建空集合
empty_st = set()
print(empty_st) # set()
# 传入字典时,返回字典的键构成的集合
st = set({1:'one', 2:'two', 3:'three'})
print(st) # {1, 2, 3}
无法索引,因此无法访问到指定元素
set.add()
增加一个元素st = {0, 1, 2, 3}
st.add(4)
print(st) # {0, 1, 2, 3, 4}
set.remove()
移除一个元素,若元素不存在,则报错st = {0, 1, 2, 3}
st.remove(1) # 若元素不在集合中则报错 KeyError
print(st) # {0, 2, 3}
set.discard()
移除一个元素,若元素不存在,啥也不做st = {0, 1, 2, 3}
st.discard(4)
print(st) # {0, 1, 2, 3}
set.pop()
移除并返回集合中的任意一个元素st = {0, 1, 2, 3}
print(st.pop()) # 0
print(st) # {1, 2, 3}
set.clear()
清空集合,使之成为空集st = {0, 1, 2, 3}
st.clear()
print(st) # set()
st1 = {0, 1, 2}
st2 = {1, 2, 3}
print(st1 & st2) # {1, 2}
st1 = {0, 1, 2}
st2 = {1, 2, 3}
print(st1 | st2) # {0, 1, 2, 3}
st1 = {0, 1, 2}
st2 = {1, 2, 3}
print(st1 - st2) # {0}
st1 = {0, 1, 2}
st2 = {1, 2, 3}
print(st1 ^ st2) # {0, 3}
<=
与 <
一个集合是另一个的子集,后者更严格(还要求两集合不相同)st1 = {0, 1, 2, 3}
st2 = {0, 1, 2, 3}
st3 = {0, 1, 2, 3, 4}
print(st1 <= st2, st1 < st2) # True, False
print(st1 <= st3, st1 < st3) # True, True
>=
与 >
一个集合是另一个的超集,后者更严格(还要求两集合不相同)st1 = {0, 1, 2, 3, 4}
st2 = {0, 1, 2, 3, 4}
st3 = {0, 1, 2, 3}
print(st1 >= st2, st1 > st2) # True, False
print(st1 >= st3, st1 > st3) # True, True
解析式(comprehension),又称推导式,使生成列表、字典、集合的代码更加简洁。此外,还有生成器解析式。
又称列表推导式,其语法为 [expression for value in iterable if condition]
,其中 if 条件为可选。(if 分支和 for 迭代结构参考 Python基础(四)控制结构)
# 生成一个列表,其元素为 10 以内的偶数的平方
[i**2 for i in range(10) if i % 2 == 0]
# [0, 4, 16, 36, 64]
# 创建 4 x 3 的二维列表
[[0] * 3 for _ in range(4)]
# [[0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]]
语法为 {key: value for (key, value) in iterable if condition}
,其中 if 条件为可选。
{i: i + 1 for i in range(3)}
# {0: 1, 1: 2, 2: 3}
语法为 {value for value in iterable if condition}
,其中 if 条件为可选。
# 生成 10 以内的奇数的集合
{i for i in range(10) if i % 2 != 0}
# {1, 3, 5, 7, 9}
解析式的创建是一个变化的过程,而元组是不可变的,因此没有元组推导式。尝试解析式的语法无法得到元组,而是将得到生成器(generator)。生成器的相关内容参考Python基础(十)深浅拷贝与三大器 Part 3
gen = (i for i in range(5))
type(gen) #
列表与元组的相同之处为,其中的元素实际上是地址,引用的内容分散地存放在内存其他位置,保证了这些组合数据类型能够容纳不同类型的数据作为元素。
它们之间的区别之一为,同样的长度下,列表所需要的存储空间比元组更大(使用 sys.getsizeof
函数查看对象的占用的内存,以 byte 为单位)
import sys
ls = []
print(sys.getsizeof(ls)) # 64
tp = ()
print(sys.getsizeof(tp)) # 48
import sys
ls = [1, 'two', {3: 'three'}]
print(sys.getsizeof(ls)) # 88
tp = (1, 'two', {3: 'three'})
print(sys.getsizeof(tp)) # 72
原因在于,由于列表是可变的,系统总会为其额外分配一些内存(over-allocating),而对于元组并不会
ls = []
for i in range(10):
print(sys.getsizeof(ls))
ls.append(i)
# 64
# 96
# 96
# 96
# 96
# 128
# 128
# 128
# 128
# 192
tp = ()
for i in range(10):
print(sys.getsizeof(tp))
tp += (i,)
# 48
# 56
# 64
# 72
# 80
# 88
# 96
# 104
# 112
# 120
字典较列表的索引速度更快,原因在于,字典中元素的存储是稀疏的,使用哈希函数的方式通过键对值进行索引,而列表中地址元素的存储是连续的,每次索引都需要从第一个元素开始。
例 列表和字典的索引速度差别
import time
ls = list(range(30000))
ls_ = list(range(30000))
since = time.time()
count = 0
for each in ls_:
if each in ls:
count += 1
print('time:', time.time() - since)
# time: 3.642605781555176
import time
dc = {i:i for i in range(30000)}
ls_ = list(range(30000))
since = time.time()
count = 0
for each in ls_:
if each in dc:
count += 1
print('time:', time.time() - since)
# time: 0.0056841373443603516
哈希函数的内容参考fishc的你知道 Python 的字典(Dict)是如何存储的吗。
所以
字典是无序的,hash 值对应项的顺序与字典项显示的顺序可能不同
字典需要的空间比列表大,实现了以空间换时间
ls = [1, 2, 3]
print(sys.getsizeof(ls)) # 88
dc = {0: 1, 1: 2, 2: 3}
print(sys.getsizeof(dc)) # 240