列表

  • 列表有序、连续地储存每个元素地址(列表对象的值是元素对象的地址),可以改变储存内容
  • 字符串可看作一种列表

列表的创建

  1. 基本语法[]创建

    list = ['python', 'java', 'object-c']
  2. 通过函数list()转换可迭代对象

    list = list(range(101)) # range对象
    list = list("Python") # 字符串对象
  3. 列表推导式生成

    list = [x**2 for x in range(10) if x%2==0]

range()函数

range([start], end, [step])
  • 创建从start(包含)到end(不包含)且步长为step的包含一系列整数的range类型对象
  • end为必选项,其它参数可选
  • 必须通过list()转换为列表,否则仍然是range类型(容易掉坑)

元素的添加

  1. 不生成新列表

    1. 末尾添加

      1. 单个元素:[0,1].append(2)
      2. 多个元素:[0,1].extend([2,3])
    2. 插入添加

      >>> print([0,1,2,3].insert(2,'a'))
      None
      
      '''
      为什么是None,而不是[0,1,'a',2,3]呢?
      因为“不生成新的列表”,所以insert()函数的返回值是None,而非新的列表。而print()函数打印的是这个返回值。
      这是容易犯的错误,其它类型的函数同理。
      '''
      
      # 正确操作如下:先赋值,再调用,最后打印
      >>> l = [0,1,2,3]
      >>> l.insert(2,'a')
      >>> print(l) # 此时打印的是“list的值”而不是“insert()返回的None”
      [0, 1, 'a', 2, 3]
  2. 生成新列表

    1. 使用+运算:[0,1]+[2]
    2. 使用*运算:[0,1]*2

补充:为什么末尾添加的效率高,而插入添加的效率低?

因为列表是连续的,就像一列无限高的书柜,从底部开始放置书籍(元素的地址),每格只能放置一本书。如果想要往某一格中插入书,就必须把格子以上所有的书取出来重新放置。插入是这样子的,后续如果我们不需要某本书(删除),同理操作。

元素的删除

  1. del list[2]:删除第3个元素,效率低
  2. list.pop(2):删除第3个元素并返回该元素的值;若留空则删除最末尾的元素,此时效率高
  3. list.remove("abc"):删除首次出现、值为"abc"的元素

元素的访问和计数

list = [3,4,5,6]

# 1. 直接通过索引
a = list[0] # 结果为3

# 2. index(value,[start,[end]])获取首次出现的位置
b = list.index(5,1,3) # 结果为2

# 3. count()获取出现的次数
c = list.count(4) # 结果为1

len(list) # 结果为4
2 not in list # 结果为True

列表的切片

详见变量的基本类型中字符串部分

复制列表可以使用list[:],获得新的列表

列表的遍历

l = list("I love Python!")
for i in l:
    print(i, end=' ')

列表的排序

  1. 不生成新列表

    1. 升序(或降序)排序:list.sort([reverse=True])
    2. 随机顺序:

      import random
      random.shuffle(list)
  2. 生成新列表

    1. 升序排序:sorted(list)
    2. 降序排序:sorted(list,reverse=True)
  3. 通过迭代器(逆序)

    >>> a = list(range(10))
    >>> a
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    >>> b = reversed(a)
    >>> b
    <list_reverseiterator object at 0x00000242D8C28C70>
    
    >>> c = list(b)
    >>> c
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    
    >>> d = list(b)
    >>> d
    []
    注意:reversed()生成的迭代器仅能使用1

列表的内置函数

l = list(range(10))

max(l)
min(l)
sum(l)

多维列表

>>> l = [
...     ['P','Y','T','H','O','N'],
...     [1,2,3,4,5,6],
...     [True,False]
... ]
>>> l[0][1]
'Y'

元组

  • 列表可变,元组不可变
  • 因为元组不可变,所以访问速度更快
  • 因为元组不可变,所以可作为字典的键

除此之外两者几乎相等,下文只提及差异

元组的创建

  1. 基本语法()创建,但存在特殊情况

    # 特殊情况1:不加括号也行,注意与序列解包赋值区分
    t1 = (1,2)
    t2 = 1,2
    
    # 特殊情况2:单个整型或浮点型需加逗号,否则不能识别为元组
    t3 = (1) # <class 'int'>
    t4 = (1,) # <class 'tuple'>
  2. 通过函数tuple()转换可迭代对象

    t1 = tuple(range(101)) # range对象
    t2 = tuple("Python") # 字符串对象
  3. 生成器推导式生成

    tuple = tuple((x**2 for x in range(10) if x%2==0)) # 为什么额外加上tuple()呢?

生成器推导式生成listtuple

# 列表推导式直接生成list
l1 = [x for x in range(5)] # <class 'list'>
# 生成器推导式间接生成list
type((x for x in range(5))) # <class 'generator'>
l2 = list((x for x in range(5))) # <class 'list'>

# 元组推导式直接生成tuple?
t1 = (x for x in range(5)) # <class 'generator'>
# 生成器推导式间接生成tuple
type((x for x in range(5))) # <class 'generator'>
t2 = tuple((x for x in range(5))) # <class 'tuple'>

# 生成器推导式的__next__()方法
>>> g = (x for x in range(3))
>>> g.__next__()
0
>>> g.__next__()
1
>>> g.__next__()
2
>>> g.__next__()
Traceback (most recent call last):
  File "<python-input-7>", line 1, in <module>
    g.__next__()
    ~~~~~~~~~~^^
StopIteration

'''
总结:
1. list既可以通过列表推导式直接生成,也可以通过生成器推导式间接生成
2. tuple没有元组推导式,只能通过生成器推导式间接生成
3. 需要间接生成list或tuple时,生成器推导式只能使用1次
4. 生成器推导式内置遍历方法__next__()

元素的增删改

因为元组不可变,所以其元素不支持增删改

元组的排序

因为元组不可变,所以无内置的tuple.sort([reverse=True])方法,只能通过sorted()进行排序

字典

三种序列的区别(除字符串、集合外)

  • 列表是有序可变连续的序列
  • 元组是有序不可变连续的序列
  • 字典是无序可变不连续的序列

字典的定义和本质

  1. 字典的元素是键值对(key-value),每一个键值对既包括键对象也包括值对象
  2. 字典通过键对象寻找值对象,类似于列表、元组通过内置索引(如0,1,2…)寻找元素对象
  3. 键对象不能改变的对象,元组可以作为键对象,而列表不能作为键对象键对象不能重复

    值对象是可以改变或不可以改变的对象,也可以重复。

字典的计算机底层原理

  1. 组成字典的基本单元是bucket(存储桶),每个bucket存储一个键对象和一个对应的值对象键对象空间存放完整的哈希值,值对象空间存放所指向对象的地址

存储字典的键值对

  1. 内存划分

    1. 字典的本质是散列表,散列表是一个不连续的数组,所以每个bucket可以存储空内容。
    2. 创建字典,一次性向内存请求划分2**n个bucket所占据的存储空间。且该存储空间是可动态更新的,一旦存放了接近2/3存储空间的bucket,会自动扩容。这种扩容不是在原有数组上操作,而是重新请求划分更大的空间,并重新计算偏移量、复制原有内容到新数组的空间。
  2. 键对象取哈希值

    1. 因为基本单元bucket的结构、大小是统一的,所以可以通过偏移量寻找指定的bucket(例如,每本书厚5cm,从左往右竖着放,可通过“第一本书起始向右偏移10cm”找到第三本书的起始)。
    2. 因为键对象可以是任一类型,而计算机只能存储二进制整型,所以通过取键对象哈希值、再转换为二进制id=bin(hash(key))的方式,获取键对象唯一的哈希值(类似于身份证)及其后n位(身份证后n位),将哈希值后n位转换为十进制dec(id[-n:])即可得到偏移量。
  3. 根据偏移量找到具体的存储空间,如果bucket没有存储内容(存储空内容),则在键对象存储完整的哈希值,在值对象存储内容的地址(哈希值后n位不用存储)。如果已经有了存储内容,则执行扩容操作,哈希值后n位会更多,直到偏移量对应的bucket没有内容。

总结解释

  1. 2.2键对象必须可散列且不能改变、重复的原因,因为需要靠键对象确定偏移量,否则找不到或者找错对应bucket。
  2. 这种操作是典型的空间换时间,牺牲巨量的内存,换取极速的查询。
  3. 不要同时修改、遍历字典,有可能会使内存溢出(扩容)、运行缓慢。

查询字典的键值对

  1. 计算给出键对象的偏移量等过程同理,不同的是即使得到相同的哈希值后n位,计算机一定会再次核对完整的哈希值,防止重复,毕竟身份证后n位也有可能相同。

字典的创建

建议初学者先看后文的zip()函数专题,再从此开始看。本章节较为困难,反复多读几遍。
  1. 基本语法{}创建

    dict_ex = {'a': 100, 'b': 200, 'c': 300}
  2. 通过函数dict()转换

    # 传入关键词参数
    dict_ex1 = dict('a'=100,'b'=200,'c'=300)
    # 传入含元组的序列(含元组的序列可替换为含元组的zip对象)
    dict_ex2 = dict([('a',100),('b',200),('c',300)])
  3. 通过函数zip()创建

    key = ['a','b','c']
    value = [100,200,300]
    kv = zip(key,value) # <zip object at 0x000001DD93ED1F00>
    dict_ex = dict(kv) # {'a': 100, 'b': 200, 'c': 300}
  4. 通过方法fromkeys()创建空值字典

    dict_ex = dict.fromkeys(['a','b','c'])
    print(dict_ex) # {'a': None, 'b': None, 'c': None}

函数zip()生成listtupledict

zip()将传入的多个列表按位置顺序打包成元组,返回的是zip对象

若列表的元素个数不一致,则以最短的列表个数作为索引截止,所有列表的超出该索引的所有元素直接丢弃

a = [1,2,3]
b = ['a','b','c','d']
c = [100,200,300,400,500,600]

# 生成list
zipped = zip(a,b,c)
print(zipped) # <zip object at 0x000001DD93EEA340>
list_ex = list(zipped)
print(list_ex) # [(1, 'a', 100), (2, 'b', 200), (3, 'c', 300)]

# 生成tuple
zipped = zip(a,b,c)
print(zipped) # <zip object at 0x000002882E379A40>
tuple_ex = tuple(zipped)
print(tuple_ex) # ((1, 'a', 100), (2, 'b', 200), (3, 'c', 300))

# 生成dict见上文

元素的访问和计数

dict_ex = {'a': 100, 'b': 200, 'c': 300}

# 通过key索引访问value
dict_ex['a'] # 100
# 通过get方法访问value
dict_ex.get('c') # 300
dict_ex.get('d','我是缺省值') # '我是缺省值'

# 枚举所有的键和(或)值
dict_ex.items() # dict_items([('a', 100), ('b', 200), ('c', 300)])
dict_ex.keys() # dict_keys(['a', 'b', 'c'])
dict_ex.values() # dict_values([100, 200, 300])

len(dict_ex) # 结果为3
'a' not in dict_ex # 结果为False
推荐使用get()方法,使用索引访问若无法找到key会报错,而get()方法不会如此且支持自定义缺省值

元素的增删改

dict_ex = {'a': 100, 'b': 200, 'c': 300}

# 单个元素的增加或覆盖
dict_ex['a'] = 400
dict_ex['d'] = 1000
# 多个元素的增加或覆盖
dict_addons = {'c':400, 'd':500, 'e':600}
dict_ex.update(dict_addons)

# 指定删除k-v
del(dict_ex['a']) # 单个元素,不返回被删除的值
dict_ex.pop('a') # 单个元素,返回被删除的值
dict_ex.clear() # 所有元素
# 随机删除k-v
dict_ex.popitem()
'''
注意,虽然是该方法是随机的,但是本质是删除散列表最后一个储存的k-v,详见“字典的定义和本质”
'''

序列解包赋值专题拓展

序列解包赋值,原则上可以用所有广义上的序列
  1. 变量的序列解包赋值:a,b = 100, 200 # 变量vs元组
  2. 序列(列表、元组)的序列解包赋值:(a,b,c) = "str" # 元组vs字符串
  3. 序列(字典)的序列解包赋值

    '''
    默认对key进行操作
    '''
    
    dict_ex = {'a': 100, 'b': 200, 'c': 300}
    p, q, r = dict_ex
    print(p) # a
    p, q, r = dict_ex.keys()
    print(p) # a
    p, q, r = dict_ex.values()
    print(p) # 100

行列表专题拓展

问题:表格如下,请利用序列知识创建行列表,并打印学号。
姓名学号班级
卢本伟101A1
羅大佐102B1
蔡徐坤103C1
'''
示例代码,手动敲一遍
'''

row1 = {'name':'卢本伟','series':101,'class':'A1'}
row2 = {'name':'羅大佐','series':102,'class':'B1'}
row3 = {'name':'蔡徐坤','series':103,'class':'C1'}
table = [row1,row2,row3]

for row in table:
    print(row.get('series'),end='\t')

集合

  1. 集合的元素是字典键对象,所以不能重复
  2. 集合的创建:

    # 基础语法方式
    set_ex = {0,1,2}
    # set()函数实现自动删除重复元素
    set_ex = set([0,1,1,3,4,2,5]) # {0, 1, 2, 3, 4, 5}
  3. 集合元素的添加:set_ex.add(4)
  4. 集合元素的删除:

    # 单个
    set_ex.remove(0)
    # 整个
    set_ex.clear()
  5. 集合元素的操作:

    # 并集
    a | b
    a.union(b)
    # 交集
    a & b
    a.intersection(b)
    # 差集
    a - b
    a.difference(b)
最后修改:2026 年 03 月 31 日
如果觉得我的文章对你有用,请随意赞赏