最近哈士奇在捡回之前Python学习的时候的基础知识,正好把列表、元组、集合、字典四大容器捡回来看看,记录一下重温的内容。其他的各种序列、容器不加进来。再补充一下,当前看到的版本是基于python 3.16的源码版本
列表
列表就是对应着我们常说的数组也就是 [ ] ,和我们常用的数组区别不大,它是可以被修改的,实际上在python的底层中,Python列表存储的是连续的指针数组,Python存入的内容是指针指向的是存储内容的地址,因此我们可以发现python的数组可以同时存入多种不同的内容。
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...这样的平滑增长方式)
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
list.append(a)
list1 = ['a', 'b', 'c']
list1.append('d')
print ("更新后的列表 : ", list1)
# 更新后的列表 : ['a', 'b', 'c', 'd']
clear()
清空列表
line 873 list_clear_impl
list.append(a)
list1 = ['a', 'b', 'c', 'd']
list1.clear()
print ("列表清空后 : ", list1)
# 列表清空后:[]
copy()
复制列表
line 696 list_slice_lock_held
for (i = 0; i < len; i++) {
PyObject *v = src[i];
dest[i] = Py_NewRef(v);
}
list1 = ['a', 'b', 'c', 'd']
liest2 = list1.copy()
print ("list2列表 : ", list2)
# list2列表 :['a', 'b', 'c', 'd']
本质上是创建了一个新的内存空间然后把每个内容逐一复制进去,因为是赋值操作(=)所以如果列表里面还有列表那么就是浅拷贝
给大家举个例子
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()也没法影响(它只能管一层,不能每层都管到)
x = []
a = [x]
b = a.copy() # 浅拷贝
x.append(1)
# a == [[1]], b == [[1]]
那么如何让每层都能拷贝到呢?
方法一:
import copy;
copy.deepcopy(obj)
方法二:
#手写深拷贝
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
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)
list1 = ['a', 'b', 'c']
list2=list(range(5)) # 创建 0-4 的列表
list1.extend(list2) # 扩展列表
print ("扩展后的列表:", list1)
#
扩展后的列表: ['a', 'b', 'c', 0, 1, 2, 3, 4]
# 语言列表
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写在尾部
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循环比较内容进行取下标
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
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到末尾指针整体向前移位; -
删除后如果实际长度不足分配容量一半,触发缩容释放内存。
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循环遍历列表,逐个对比元素;
-
找到第一个匹配项后,后续元素指针前移覆盖;
-
找不到直接抛出异常
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
原地反转列表元素顺序,无返回值,不生成新列表。
实际上是 双指针:左指针从头、右指针从尾,交换两边指针,直到两指针相遇。
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
原地稳定排序,无返回值
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']