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']
相关推荐
笨鸟先飞,勤能补拙3 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能3 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂4 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study4 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境5 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境5 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1235 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102166 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb006 小时前
一个项目带你入门AI应用开发01
python
0566466 小时前
Python康复训练——常用标准库
开发语言·python·学习