Python 四大基础容器|列表篇

最近哈士奇在捡回之前Python学习的时候的基础知识,正好把列表、元组、集合、字典四大容器捡回来看看,记录一下重温的内容。其他的各种序列、容器不加进来。再补充一下,当前看到的版本是基于python 3.16的源码版本

列表

列表Python源码地址

列表就是对应着我们常说的数组也就是 ,和我们常用的数组区别不大,它是可以被修改的 ,实际上在python的底层中,Python列表存储的是连续的指针数组,Python存入的内容是指针指向的是存储内容的地址,因此我们可以发现python的数组可以同时存入多种不同的内容。

C 复制代码
struct PyListObject { 
    PyObject_VAR_HEAD 
    PyObject *ob_item[]; // 指针数组:每个元素存对象地址 
    Py_ssize_t ob_allocated; // 分配总容量(预留空间) 
};

列表增长方式

Python列表有自动扩/缩容机制(参考源码文件 line 104 的 list_resize 方法),在数组增长的时候会自动调用list_resize方法自动分配新的内存(增长方式主要是0, 4, 8, 16, 24, 32, 40, 52, 64, 76...这样的平滑增长方式)

c 复制代码
new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;

新容量大致按这个公式增长:

  • newsize:实际需要的元素数
  • newsize >> 3:额外加上 1/8
  • + 6:小列表时再多给一点
  • & ~3:向下/对齐到 4 的倍数

所以容量是 约 1.125 倍 + 常数能保证不会浪费太多内存资源

列表的方法

(参考源码文件 line 3597 的 list_methods\[\])

  • append(x)
  • clear()
  • copy()
  • count(x)
  • extend(iterable)
  • index(x[, start[, stop]])
  • insert(i, x)
  • pop([i])
  • remove(x)
  • reverse()
  • sort(*, key=None, reverse=False)

append(x)

向列表添加 x

line 1232 list_append_impl

python 复制代码
    list.append(a)
python 复制代码
list1 = ['a', 'b', 'c']  
list1.append('d')  
print ("更新后的列表 : ", list1)
# 更新后的列表 :  ['a', 'b', 'c', 'd']

clear()

清空列表

line 873 list_clear_impl

python 复制代码
    list.append(a)
python 复制代码
list1 =  ['a', 'b', 'c', 'd']
list1.clear()
print ("列表清空后 : ", list1)
# 列表清空后:[]

copy()

复制列表

line 696 list_slice_lock_held

python 复制代码
    for (i = 0; i < len; i++) {
        PyObject *v = src[i];
        dest[i] = Py_NewRef(v);
    }
python 复制代码
list1 =  ['a', 'b', 'c', 'd']
liest2 = list1.copy()
print ("list2列表 : ", list2)
#  list2列表 :['a', 'b', 'c', 'd']

本质上是创建了一个新的内存空间然后把每个内容逐一复制进去,因为是赋值操作(=)所以如果列表里面还有列表那么就是浅拷贝

给大家举个例子

python 复制代码
a=[0,1,2,3,4,5]
b=a
c=a.copy()

del a[1]  
'''
   各变量值为:
   a=[0, 2, 3, 4, 5]
   b=[0, 2, 3, 4, 5]
   c=[0, 1, 2, 3, 4, 5]
'''

b.remove(4)
'''
   各变量值为:
   a=[0, 2, 3, 5]
   b=[0, 2, 3, 5]
   c=[0, 1, 2, 3, 4, 5]
'''
c.append(9)
'''
   各变量值为:
   a=[0, 2, 3, 5]
   b=[0, 2, 3, 5]
   c=[0, 1, 2, 3, 4, 5, 9]
'''

这里面大家会发现,ab之间的修改是会互相影响的,因为b对a进行了赋值操作,把a的内存地址复制过来了,所以ab是一样的,改动也会互相影响,但是使用copy()的话,会在内存里面创建新的地址,再把老的列表内容逐一赋值进去,所以c和ab的变化没关系

但是出现了列表里面套数组/对象这样的情况的时候,copy()也没法影响(它只能管一层,不能每层都管到)

python 复制代码
x = []
a = [x]
b = a.copy()   # 浅拷贝
x.append(1)
# a == [[1]], b == [[1]]

那么如何让每层都能拷贝到呢?

方法一:

python 复制代码
import copy; 
copy.deepcopy(obj)

方法二:

python 复制代码
#手写深拷贝
def deep_copy(obj, memo=None):
    # memo:缓存已经拷贝过的对象,解决循环引用
    if memo is None:
        memo = {}

    # 如果该对象已经拷贝过,直接返回副本,避免循环引用死循环
    if id(obj) in memo:
        return memo[id(obj)]

    # 1. 基础不可变类型,直接返回自身
    basic_types = (int, float, str, bool, type(None))
    if isinstance(obj, basic_types):
        return obj

    # 2. 列表
    if isinstance(obj, list):
        new_list = []
        memo[id(obj)] = new_list  # 先存入缓存,处理内部循环引用
        for item in obj:
            new_list.append(deep_copy(item, memo))
        return new_list

    # 3. 字典
    if isinstance(obj, dict):
        new_dict = {}
        memo[id(obj)] = new_dict
        for k, v in obj.items():
            new_dict[deep_copy(k, memo)] = deep_copy(v, memo)
        return new_dict

    # 4. 集合
    if isinstance(obj, set):
        new_set = set()
        memo[id(obj)] = new_set
        for item in obj:
            new_set.add(deep_copy(item, memo))
        return new_set

    # 5. 元组(不可变,但内部有可变元素也要递归拷贝)
    if isinstance(obj, tuple):
        temp = [deep_copy(item, memo) for item in obj]
        new_tuple = tuple(temp)
        memo[id(obj)] = new_tuple
        return new_tuple

    # 6. 自定义类实例通用拷贝
    if hasattr(obj, "__dict__"):
        # 创建空实例
        new_obj = obj.__class__()
        memo[id(obj)] = new_obj
        # 递归拷贝所有属性
        for attr_name, attr_val in obj.__dict__.items():
            setattr(new_obj, attr_name, deep_copy(attr_val, memo))
        return new_obj

    # 其他不识别对象直接返回原对象
    return obj

count()

计算列表元素的个数(for循环取数)

line 3366 list_count_impl

python 复制代码
aList = [123, 'Google', 'Runoob', 'Taobao', 123];  
  
print ("123 元素个数 : ", aList.count(123))  
print ("Runoob 元素个数 : ", aList.count('Runoob'))

'''
123 元素个数 :  2
Runoob 元素个数 :  1
'''

extend()

给列表后面加入新的内容

line 1457 list_extend_impl( 实际逻辑 _list_extend)

python 复制代码
list1 = ['a', 'b', 'c']
list2=list(range(5)) # 创建 0-4 的列表
list1.extend(list2)  # 扩展列表
print ("扩展后的列表:", list1)
# 
扩展后的列表: ['a', 'b', 'c', 0, 1, 2, 3, 4]
python 复制代码
# 语言列表
language = ['French', 'English', 'German']
 
# 元组
language_tuple = ('Spanish', 'Portuguese')
 
# 集合
language_set = {'Chinese', 'Japanese'}
 
# 添加元组元素到列表末尾
language.extend(language_tuple)
 
print('新列表: ', language)
 
# 添加集合元素到列表末尾
language.extend(language_set)
 
print('新列表: ', language)

在extend里面 加入字典会按照字典迭代顺序把key写在尾部

python 复制代码
a = [1, 2]
d = {"x": 10, "y": 20}
a.extend(d)
# a == [1, 2, "x", "y"]

index(x,, start)

返回 x 在列表中第一次出现的下标(在 start:stop 范围内找)。找不到抛 ValueError

line 3325 list_index_impl

本质上是通过for循环比较内容进行取下标

python 复制代码
list1 = ['a', 'b', 'c']
print ('a 索引值为', list1.index('a'))
print ('b 索引值为', list1.index('b'))
'''
a 索引值为 0
b 索引值为 1
'''
list1 = ['a', 'b', 'c', 'd', 'e']  
# 从指定位置开始搜索  
print ('b 索引值为', list1.index('b',1))

# b 索引值为 1

insert(i, x)

line 1183 list_insert_impl(实际是 ins1)

在下标 i 处插入元素 x,原下标 i 及后面所有元素向后整体移位一位,原地修改列表

  • 先判断当前分配容量 allocated 是否还有空位;

  • 无空位调用 list_resize 自动扩容;

  • 使用内存拷贝,把 i 到末尾的指针整体向后挪一格;

  • 把新元素指针写入 ob_item[i],长度 +1

python 复制代码
lst = ['a', 'b', 'c']
# 下标0插入元素
lst.insert(0, 'start')
print(lst)  # ['start', 'a', 'b', 'c']

# 下标2插入
lst.insert(2, 'mid')
print(lst)  # ['start', 'a', 'mid', 'b', 'c']

# 插入到末尾,等价 append()
lst.insert(len(lst), 'end')
print(lst)  # ['start', 'a', 'mid', 'b', 'c', 'end']

# 下标超出长度,自动插在最后
lst.insert(999, 'last')
print(lst)


'''
['start', 'a', 'b', 'c']
['start', 'a', 'mid', 'b', 'c']
['start', 'a', 'mid', 'b', 'c', 'end']
['start', 'a', 'mid', 'b', 'c', 'end', 'last']
'''

pop(i)

line 1326 list_pop_impl

删除并返回下标 i 对应的元素;不传参数默认删除最后一位

  • pop ():直接取最后一个元素,长度 -1,均摊 O (1)

  • 指定下标 pop (i):取出元素,i+1 到末尾指针整体向前移位;

  • 删除后如果实际长度不足分配容量一半,触发缩容释放内存。

py 复制代码
lst = [10, 20, 30, 40]
# 默认删末尾
res1 = lst.pop()
print(res1, lst)  # 40 [10, 20, 30]

# 删除下标1
res2 = lst.pop(1)
print(res2, lst)  # 20 [10, 30]

# 下标不存在抛 IndexError
# lst.pop(99)

remove(x)

line 1403 list_remove_impl

原地删除列表中第一个匹配 x 的元素;遍历完没找到则抛 ValueError

  • 从头for循环遍历列表,逐个对比元素;

  • 找到第一个匹配项后,后续元素指针前移覆盖;

  • 找不到直接抛出异常

python 复制代码
lst = [2, 5, 3, 5, 1]
lst.remove(5)
print(lst)  # [2, 3, 5, 1]

# lst.remove(99)  # ValueError: list.remove(x): x not in list

reverse()

line 3276 list_reverse_impl

原地反转列表元素顺序,无返回值,不生成新列表。

实际上是 双指针:左指针从头、右指针从尾,交换两边指针,直到两指针相遇。

python 复制代码
lst = [1, 2, 3, 4]
lst.reverse()
print(lst)  # [4, 3, 2, 1]

# 对比切片反转(生成新列表,不修改原列表)
lst2 = [1, 2, 3]
new_lst = lst2[::-1]
print(lst2, new_lst)  # [1,2,3] [3,2,1]

sort(*, key=None, reverse=False)

line 3424 list_sort_impl

原地稳定排序,无返回值

python 复制代码
nums = [5, 2, 9, 1]
nums.sort()
print(nums)  # [1, 2, 5, 9]

nums.sort(reverse=True)
print(nums)  # [9, 5, 2, 1]

# key 自定义排序
words = ["banana", "apple", "pear"]
# 按字符串长度排序
words.sort(key=lambda s: len(s))
print(words)  # ['pear', 'apple', 'banana']
相关推荐
明理的信封2 小时前
AI 基础设施的“去 Python 化“:Rust 与 C# 的两条替代路径
人工智能·python·rust
麻雀飞吧3 小时前
2026年AI量化开发,先跑通小流程再加复杂功能
人工智能·python
daphne odera�3 小时前
PyCharm 中 Codex 插件启动失败:unknown variant default 的解决方法
python·chatgpt·pycharm
nbu04william4 小时前
Deepseek-api省token的用法
python·大模型·token·deepseek
测试老哥4 小时前
Pytest自动化测试详解
自动化测试·软件测试·python·测试工具·测试用例·pytest·接口测试
坚持学习前端日记4 小时前
国产化适配全流程适配英伟达本地开发
人工智能·python
源图客5 小时前
云途物流API开发-鉴权认证(Java)
java·网络·python
三川6986 小时前
Tkinter库的学习记录06-变量类别
python
wenying_443237446 小时前
Python脚本—1.提取压缩包中的pdf文件
python·ai编程