当前位置: 首页 > news >正文

【Python】存储机制和容器四大件列表、元组、字典、集合

文章目录

    • 1 Python中的引用机制
    • 2 列表List
      • 2.1 列表的底层实现
      • 2.2 列表的基本使用
      • 2.3 列表的深拷贝和浅拷贝
      • 2.4 列表推导式
    • 3 元组Tuple
      • 3.1 元组和列表的区别
      • 3.2 元组的内存机制
      • 3.3 元组和字符串
    • 4 字典Dict
      • 4.1 字典的常用操作
      • 4.2 字典的常见错误
    • 5 集合set
      • 5.1 集合的常用操作

1 Python中的引用机制

Python中变量是对象的引用,也就是说创建一个变量不会直接存储值,而是存储一个地址指向内存中的对象。

m = 5
n = m
m = 30
print(n) #输出:5

对于对象而言,如果有多个变量存的地址指向这个对象,那么这个对象就会有一个引用计数,当引用计数为0时,对象销毁。

import sys 
a = []  # 对象创建,引用计数=1 
b = a   # 引用计数+1 → 2 
print(sys.getrefcount(a)) # 查看引用计数输出3

上面引用计数为什么不是2?
因为Python 解释器会内部缓存一些简单对象比如上面的空列表[],为了复用内存地址以提升性能。

在之后创建列表、元组、字典、集合这些对象时,其实都是变量存放一个地址指向对象。

2 列表List

2.1 列表的底层实现

  • 列表底层就是一个动态数组,其有着有效数据长度、空间容量、元素类型等组成。
  • 列表内有着连续的内存块,其中存储着每个元素的内存地址引用(并非实际数据),可以通过索引快速定位。
  • 列表中数据存储的内存地址是连续的,但是其实际数据会分散在内存的不同位置。

  列表作为一个动态数组,在容量不足时会进行扩容,扩容系数在1.125-1.5之间,为的是避免频繁扩容。扩容时,系统会申请一块新的空间,将列表原有的数据(里面存放着指向实际数据的地址)拷贝过去,再改变list中ptr的指向(但list的地址不会改变)后释放原有内存。(整个过程是一个浅拷贝)

2.2 列表的基本使用

列表在使用上和数组类似,不同的是它可以存储多种类型的数据,先让我们快速看一下如何使用列表

#列表是可变类型,可添加各种可变(列表、字典)和不可变的类型(字符串、元组)
##########################################
#append添加
list1 = ["样例1"]
list1.append("样例2")
list1.append('a')
list1.append(1)
list1.append(["样例3","样例4"])
print(list1) # 输出:['样例1', '样例2', 'a', 1, ['样例3', '样例4']]##########################################
#insert按位置插入
list1.insert(len(list1), "样例5")#[]下标访问
list1[2] = 'b'
print(list1) # 输出:['样例1', '样例2', 'b', 1, ['样例3', '样例4'], '样例5']##########################################
#切片
list2 = list1[0:5:2] #[start:end:gap] start位置到end前一个位置每次隔了gap个
list3 = list1[:2] #start不写默认从头开始,end不写默认到尾部,gap不写默认为1
print(list2) # 输出:['样例1', 'b', ['样例3', '样例4']]print(list3) # 输出:['样例1', '样例2']##########################################
#删除元素
list1.remove("b") #没有会报错
#遍历
for e in list1:print(e,end=' ')
print()
for i in range(len(list1)):print(list1[i],end=' ')
print()
#二者都输出:样例1 样例2 1 ['样例3', '样例4'] 样例5##########################################
# +和*
list4 = list2 + list3
list5 = list3 * 2
print(list4) #['样例1', 'b', ['样例3', '样例4'], '样例1', '样例2']
print(list5) #['样例1', '样例2', '样例1', '样例2']

2.3 列表的深拷贝和浅拷贝

拷贝的时候,需要确保原始数据是否需要保护,以免出现数据意外修改。
进行深拷贝的时候,需要使用copy.deepcopy(),而浅拷贝则在切片的时候触发。

import copy
#深拷贝复制所有层的数据各不干扰
list1 = ["a",["b", "c"]]
list2 = copy.deepcopy(list1)
list2[0] = "d"
list2[1][1] = "e"
print(list1) # ['a', ['b', 'c']]#浅拷贝 只拷贝第一层的地址数据且共享深层的数据,就是说第一层其地址指向的数据不会拷贝。
list_1 = ["a",["b","c"]]
list_2  = list_1[:] # 切片触发浅拷贝
list_2[0] = "d" # 修改第一层 list_1不受影响
list_2[1][1] = "e" # 修改深层 list_1受到影响
print(list_1) # ['a', ['b', 'e']]

很明显:
深拷贝优点是不会影响原有数据,缺点是占用空间多。
浅拷贝优点是节约空间,但会影响原有数据。

2.4 列表推导式

列表推导式能有效的简化代码,将可迭代的对象转换成新列表。
用法:
新列表 = [表达式 for 变量in 可迭代对象]
新列表 = [表达式 for 变量in 可迭代对象 if 条件]
列表推导式的嵌套循环和条件判断遵从 从外到内、从左到右
下面看懂一些列表推导式就行了。

# 列表推导式 
matrix = [[i*j for j in range(3)] for i in range(4)]
print(matrix)
# 输出:[[0, 0, 0], [0, 1, 2], [0, 2, 4], [0, 3, 6]]####################
#右边带条件的
nums = [x for x in range(20) if x%2==0 if x>10] #又要整除2又要大于10#列表推导式的嵌套循环和条件判断遵从 从外到内、从左到右
pairs = [(x, y) for x in range(3) if x!=1 for y in range(3) if x+y>1]
# 输出:[(0, 2), (2, 0), (2, 1), (2, 2)]#和函数一起
def process(num):return num ** 2 + 1 
result = [process(x) for x in range(5)]  # [1, 2, 5, 10, 17]#lambda表达式 左边(lambda num: num**3)作为一个临时函数,右边(num)代表接受的参数
squares = [(lambda num: num**3)(num) for x in range(4)]  # [0, 1, 8, 27]# 矩阵转置 
matrix = [[1, 2], [3, 4], [5, 6]]
transposed = [[row[i] for row in matrix] for i in range(2)]
# 输出:[[1, 3, 5], [2, 4, 6]]# 平铺嵌套列表 
nested_list = [[1,2], [3,4,5]]
flat = [num for sublist in nested_list for num in sublist]  # [1,2,3,4,5]#all()代表所有满足
primes = [x for x in range(2, 30) if all(x % y !=0 for y in range(2, int(x**0.5)+1))]
# 输出:[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]colors = ['红', '蓝']
sizes = ['S', 'L']
cartesian = [(c, s) for c in colors for s in sizes]
# 输出:[('红', 'S'), ('红', 'L'), ('蓝', 'S'), ('蓝', 'L')]

3 元组Tuple

Python中元组是一种不可变的序列,其特点是不可变性以及内存高效性。不可变性就是元组一旦创建,里面的元素就不能修改(当然元组里面的可变对象比如列表,可以修改,但本身元组中存的地址是不会变的),内存高效性体现在,元组在创建时就确定好了开辟内存空间的大小,而不是像列表有预留的空间。

3.1 元组和列表的区别

  • 列表是可变序列,也就是支持增删改操作,而元组一旦创建就无法修改元素
  • 元组在创建时就确定了所有元素,其内存空间就会一次性给它分配完成。这个内存空间不可扩展或收缩是固定的,而列表的空间则是动态的,会预留额外空间。
  • 元组使用()进行表示,而列表则是 [ ] 。

下面看元组具体的一些使用

# 创建元组 单元素元组需要加','
a = (1,)
b = (1,2)
c = 1,2
d = tuple([1,2,3,3,5,7])# 一些常用的内置函数
print(len(d)) # 打印d长度
print(d.count(3)) # 打印d中3出现的个数
print(d.index(3)) # 打印d中首次出现3的下标# 元组无增删 但可以用索引和切片
a = (1,2,[3,4])
# a[0] = 6  #报错
# a[2] = [5,6] #报错
a[2][0] = 5 #可以修改
print(a) # 输出:(1, 2,[5, 4])# 元组的切片也是浅拷贝
b = a[:]
print(b) # 输出:(1, 2,[5, 4])
b[2][0] = 7
print(a) # 输出:(1, 2,[7, 4])import copy
c = copy.deepcopy(a)
print(c) # 输出:(1, 2,[7, 4])
c[2][0] = 8
print(a) # 输出:(1, 2,[7, 4])

3.2 元组的内存机制

元组作为不可变数据也会发生“变化”,在添加了元素后,会创建新的元组。而列表作为可变数据则不会“改变”

# 新的元组有着不同的地址
tuple01 = ("西瓜", "苹果")
print(id(tuple01)) #3031491680896
tuple01 += ("草莓", "芒果")
print(id(tuple01)) #3031491260432# 列表修改为相同的地址
tuple02 = ["西瓜", "苹果"]
print(id(tuple02)) #2027854913728
tuple02 += ["草莓", "芒果"]
print(id(tuple02)) #2027854913728

内存复用机制

由于元组的不可变性,Python 解释器会缓存简单元组(如空元组 () 或小整数元组),复用内存地址以提升性能。

tuple02 = ()
tuple03 = ()
tuple04 = ()
print(id(tuple02))
print(id(tuple03))
print(id(tuple04)) #都是140717271565760tuple05 = (1,2,3,4)
tuple06 = (1,2,3,4)
tuple07 = (1,2,3,4)
print(id(tuple05))
print(id(tuple06))
print(id(tuple07)) #都是1961087315984

3.3 元组和字符串

  1. 元组和字符串均属于不可变序列,修改就会报错。
  2. 两个都可以通过下标进行访问以及进行切片操作。
  3. 由于两个都有不可变性,它们都有内存复用机制,使得在一些场景下使用的性能提高。
s = "hello"
t = (1, 2, 3)
s[0] = "H"  # 报错 
t[0] = 4    # 报错 s = "Python"
print(s[1:4]) # "yth"(正向切片)
print(s[-3:]) # "hon"(反向切片)

下面看字符串的一些操作方式

(1)字符串的分割和合并

data = "apple,banana,orange"
fruits = data.split(",")   # ['apple', 'banana', 'orange']words = ["Python", "is", "awesome"]
sentence = " ".join(words)  # "Python is awesome"

(2)strip 去除首尾指定元素

默认时,去除前后空白,包括\t和\n

s = "  hello world\t\n"
print(s.strip())   # 输出:"hello world"

也可以指定元素

s = "###hello#world###"
print(s.strip('#'))   # 输出:"hello#world"
s2 = "abc123cba"
print(s2.strip('abc'))   # 输出:"123"(首尾的a、b、c均被移除)

(对应对于lstrip()和rstrip()也可以指定方向,其他用法都是一样的)

(3)查找和替换

s = "hello world"
print(s.find("wo"))   # 输出 6text = "I like Java"
new_text = text.replace("Java",  "Python")  # "I like Python"

(4)格式化输出
f-string方式

name = "Alice"
print(f"Hello, {name.upper()}!")   # 输出:Hello, ALICE!

format方式

print("{} is {} years old".format("Bob", 25))

%方式

print("Price: $%.2f" % 99.99)  # 输出:Price: $99.99

4 字典Dict

字典作为一种可变的无序键值对集合,其以键值对{key,value}的方式存储数据。

  1. 字典中的键(key)必须不可变类型,比如元组、字符串、数字,并且唯一。若重复则后定义的会覆盖前面的。值(value)可以是任意类型。
  2. 字典是可变的,支持动态增删改键值对。
  3. 字典的查询效率很高是O(1),因为其底层是采用哈希表。因为引用键值对的地址不是连续的因此不可索引访问。

4.1 字典的常用操作

字典的创建

# 1. 直接定义键值对
person = {"name": "Tom", "age": 22, "city": "Shanghai"} # 2. 使用 dict 构造函数
config = dict(host="localhost", port=3306)# 3. 通过 zip 组合键值
# zip作用是将多个可迭代对象的元素按索引位置一一配对,生成由元组组成的迭代器
keys = ["name", "age"]
values = ["Alice", 25]
user = dict(zip(keys, values))  # {'name': 'Alice', 'age': 25}# 4. 字典推导式快速生成
squares = {x: x**2 for x in range(5)}  # 生成 {0:0, 1:1, 2:4...}

键值访问和遍历

# 1. 直接访问(KeyError 风险)
print(person["name"])  # 输出 "Tom"# 2. 安全访问 get 方法 若没有就输出unknown
print(person.get("gender",  "unknown"))  # 输出 "unknown"# 3. 遍历键值对
for key, value in person.items(): print(f"{key}: {value}") # 4. 单独获取键/值列表
keys = person.keys()     # ["name", "age", "city"]
values = person.values()   # ["Tom", 22, "Shanghai"]

键值修改

# 1. 添加/修改键值
person["gender"] = "male"   # 新增字段
person["age"] = 23          # 修改已有字段# 2. 批量更新 update
person.update({"age":24,  "job":"Engineer"})# 3. 智能设置 setdefault
# 当键不存在时设置默认值,存在则返回当前值
count = person.setdefault("login_count",  0)

删除操作

# 1. 删除指定键
del person["city"]       # 直接删除
age = person.pop("age")  # 删除并返回值# 2. 清空字典
person.clear()   # 变为空字典 {}

4.2 字典的常见错误

(1)遍历时修改字典
因为第一个迭代器遍历的时候会跟踪字典状态,在循环中删除键值对就会导致无法正常跟踪后续元素。第二个则是生成一个静态列表与原字典解耦,可以根据列表进行正常迭代。

# 错误方式:
for key in person:del person[key]  # RuntimeError
# 正确方式:
for key in list(person.keys()): del person[key]

(2)可变对象作为键
键必须是不可变类型的。

# 错误示例:
d = {[1,2]: "value"}  # TypeError
# 正确方式:
d = {tuple([1,2]): "value"}

(3)误用浅拷贝

d1 = {"a": [1,2]}
d2 = d1.copy() 
d2["a"].append(3)  # d1 的值也会被修改!
# 正确深拷贝:
import copy
d2 = copy.deepcopy(d1)

5 集合set

Python中集合set和字典dict底层都是由哈希表实现,因此集合中的元素也没有固定顺序,无法通过索引访问。set底层和dict类似都是键值对,只不过set的键和值都一样,因此这个值需要满足键的特征,因此其存储的元素具有唯一性以及只能存储不可变类型(如整数、字符串、元组),不可存放列表、字典等可变对象。

5.1 集合的常用操作

集合的创建
集合和字典一样,都用{}。

s1 = {1, 2, 3}             # 直接定义 
s2 = set([1, 2, 2, 3])     # 通过列表去重创建 → {1, 2, 3}
empty_set = set()          # 空集合必须要用set()({}是空字典)

集合的增删改查

s = {1, 2}
s.add(3)                    # 添加单个元素 → {1, 2, 3}
s.update([4,  5])           # 批量添加 → {1, 2, 3, 4, 5}s = {1, 2, 3, 4}
s.remove(3)                 # 删除指定元素(不存在则报错)
s.discard(5)                # 安全删除(元素不存在不报错)
s.pop()                     # 随机删除并返回一个元素(如1)s = {2, 4, 6}
print(2 in s)              # True(成员检查)
print(len(s))              # 输出元素数量 → 3

集合的运算
& 取交集、| 取并集、-取差集(在A不在B的)、^对称差集(仅属于A或B的)

a = {1, 2, 3}
b = {2, 3, 4}
print(a & b)               # 输出 {2, 3}
print(a | b)               # 输出 {1, 2, 3, 4}
print(a - b)               # 输出 {1}
print(a ^ b)               # 输出 {1, 4}

子集判断issubset,父集判断issuperset

x = {1, 2}
print(x.issubset(a))        # True(x是a的子集)
print(a.issuperset(x))      # True(a是x的父集)


本节需要重点了解各个容器的底层实现,其次是基本使用操作,方便后续内容的使用。

相关文章:

  • 【言语理解】中心理解题目之选项分析
  • Java捕获InterruptedException异常后,会自动清空中断状态
  • 卷积神经网络实战(2)
  • [ML]通过50个Python案例了解深度学习和神经网络
  • VulnHub-OSCP靶机
  • 【神经网络与深度学习】生成模型-单位高斯分布 Generating Models-unit Gaussian distribution
  • 超详细讲解C语言转义字符\a \b \r \t \? \n等等
  • 单调栈与单调队列(c艹)、可视化Qt?
  • Android第三次面试总结之网络篇补充
  • 使用 Hugging Face 镜像站快速下载大模型
  • emplace_back和push_back
  • 五、shell脚本--函数与脚本结构:搭积木,让脚本更有条理
  • Vultr之Ubuntu重设密码
  • 解决在 Linux 中 WPS 字体缺失问题
  • KAG:通过知识增强生成提升专业领域的大型语言模型(三)
  • 有关水下图像增强的论文
  • [特殊字符] 专业角度深入讲解:大模型备案(生成式人工智能)
  • RuntimeError: CUDA error: __global__ function call is not configured
  • 计算机图形学中的深度学习
  • 2025年- H25-Lc133- 104. 二叉树的最大深度(树)---java版
  • 特朗普考虑任命副幕僚长米勒任国安顾问,曾策划驱逐移民行动
  • 成为中国骑手孵化器,上海环球马术冠军赛是最好的历练舞台
  • 国羽3比0横扫日本晋级苏迪曼杯决赛,将战韩国与印尼胜者
  • 南京大屠杀幸存者刘贵祥去世,享年95岁
  • 阿曼外交部:美伊谈判因故推迟
  • 美国经济萎缩意味着什么?关税政策如何反噬经济?