深浅拷贝
深浅拷贝问题
1.1 定义理解
给一个列表,想基于这个列表进行更改生成一个新的列表。
方式一:将原来列表复制一份一摸一样的
只对新列表生效,对原来的不生效
num_list = [1,2,3]
num_list_new = [1,2,3]
num_list_new.append(4)
print(num_list) # [1,2,3]
print(num_list_new) # [1, 2, 3, 4]
方式二:用新的变量名指向原来的列表–>连续赋值
对原列表和新列表都生效
num_list = [1,2,3]
num_list_new = num_list
num_list_new.append(4)
print(num_list) #[1, 2, 3, 4]
print(num_list_new) # [1, 2, 3, 4]
1.2 复制原本的列表会产生的问题
新列表是复制出来的副本
【1】修改新列表会影响到原来的列表—->浅拷贝
【2】修改新列表不会影响到原来的列表—->深拷贝
1.3 在python代码中实现深浅拷贝
【1】浅拷贝
是修改源数据类型中的可变数据类型进行生效
copy.copy(x):返回一个与x相同的新对象,但并不是同一个对象。如果x是一个复合对象(例如列表、字典等),则只复制了对象的顶层,内部的元素仍然引用原始的对象。
import copy
num_list = [1, 2, 3, 4, [1, 2]]
num_list_new = copy.copy(num_list)
num_list_new[4].append(5)
print(num_list, id(num_list))
# [1, 2, 3, 4, [1, 2, 5]] 2279619271360
print(num_list_new, id(num_list_new))
# [1, 2, 3, 4, [1, 2, 5]] 2279619271616
【2】深拷贝
深拷贝就是将原来的列表完全复制一份,修改新列表不会影响到原来的列表
copy.deepcopy返回一个与x完全相同的新对象,并且递归地复制了x中的所有元素。
import copy
num_list = [1, 2, 3, 4, [1, 2]]
num_list_new = copy.deepcopy(num_list)
num_list_new[4].append(5)
print(num_list, id(num_list))
# [1, 2, 3, 4, [1, 2]] 1392648755456
print(num_list_new, id(num_list_new))
# [1, 2, 3, 4, [1, 2, 5]] 1392648755712
【总结】
-
浅拷贝只会复制顶层对象,而不会影响到深层的可变数据类型
- 浅拷贝复制出来的列表,其中列表中的列表引用的是原来列表中的列表,故而改变复制出来的列表,原来的也会改变。
-
深拷贝会递归的复制整个对象的数据结构
- 复制出来的列表,列表中的列表引用的是新的列表。
垃圾回收机制
1 什么是垃圾回收机制
垃圾回收机制是(GC机制)python自带的一种机制
专门用来回收变量值所占的内存空间
2 堆和栈的概念
堆区:变量值存放的区域
栈区:变量名和值内存地址关联的区域
【1】引用计数
变量值被变量名指向的次数
x = 10 #1
y = x #2 --> y=x=10
【2】标记清除
当一个变量值被引用的时候,python中自带的垃圾回收机制会定期扫描。
- 如果这个变量值有引用—->不管
- 如果这个变量值没有引用—>标记
【3】分代回收
- 新生代—>第一次被扫描,扔到新生代
- 青春代—>直到达到新生代扫描阈值,如果还没有指向,直接挪到青春代
- 老年代—>直到达到青春代扫描阈值,如果还没有指向,直接挪到老年代
- —->直到达到老年代扫描阈值,如果还没有指向,直接 del
# 【1】堆区
# 变量值存放于堆区,内存管理回收的则是堆区的内容
# # 在Python中,变量和它们所引用的对象(如数值、字符串、列表、字典等)的存储位置与内存管理机制密切相关。
# # 这里提到的“变量值存放于堆区”主要涉及两个概念:变量本身和它所引用的对象。
# # 1. 变量
# 变量是程序中用于标识数据的一个符号或名字,它并不直接存储数据,而是指向(或引用)实际存储数据的内存地址。
# 在Python解释器内部,变量名通常保存在栈(Stack)中。栈是一种后进先出(LIFO)的数据结构,用于高效地管理函数调用时的局部变量、返回地址等信息。
# 当创建一个变量时,Python会在当前作用域(如全局作用域或函数局部作用域)的栈空间内为该变量分配一个名称,并将其关联到相应的对象。
# # 2. 对象及值
# Python中的对象(即变量所引用的实际数据)通常存储在堆(Heap)中。
# 堆是一种动态分配内存的区域,相比于栈,其大小更为灵活且无固定上限。
# 堆用于存储需要大量、不固定大小内存空间的数据结构,如大数组、复杂的数据类型(如类实例)、以及那些生命周期可能跨越多个函数调用或作用域的对象。
# # 将对象值存放在堆区有以下几个原因:
# # - 灵活性
# 堆内存的大小可以动态调整,能够适应不同大小和复杂度的对象。
# 这使得Python可以在运行时根据需要创建任意大小的对象,无需预先确定其具体尺寸,非常适合处理变长数据结构(如列表、字典)。
# # - 共享性
# 多个变量可以同时引用同一块堆内存中的对象,实现对象的共享。
# 这种机制允许通过赋值、参数传递、返回值等方式高效地复制“引用”,而不是复制整个对象内容,节省了内存资源并提高了效率。
# # - 垃圾回收
# 由于堆内存中的对象可能存在多个引用,Python使用垃圾回收(Garbage Collection, GC)机制自动追踪和管理对象的生命周期。
# 当一个对象不再有任何引用时,GC会识别并释放其占用的堆内存,防止内存泄漏。
# 堆区便于实施这样的垃圾回收策略,确保程序长期运行时内存的有效利用。
# # 综上所述
# Python将变量值(即对象)存放于堆区,主要是为了提供灵活的内存分配、支持对象共享以及实现自动化的垃圾回收,这些特性对于编写高效、健壮的Python程序至关重要。
# 而变量本身作为对象的引用,则通常存储在栈区,便于快速访问和管理其生命周期。
# 【2】栈区
# 变量名与值内存地址的关联关系通常被存放于程序的栈区
# # 1. 栈区的特性与作用
# 栈(Stack)是计算机内存中的一种数据结构,其主要特点是后进先出(LIFO)。在编程语言执行过程中,栈区用于存储局部变量、函数参数以及函数调用时的返回地址等临时性信息。
# 栈区的操作快速且高效,因为它遵循简单的压栈(push)和弹栈(pop)规则,适合处理那些生命周期短、使用频繁且需要保持特定顺序的数据。
# # 2. 变量生命周期管理
# 变量名与值内存地址的关联关系本质上是一种符号表信息,它记录了源代码中定义的变量名与其所对应的内存地址之间的映射。
# 这种信息具有明显的生命周期特征:当进入一个作用域(如函数或代码块)时,新声明的变量及其关联关系应被创建;
# 当离开该作用域时,这些变量及关联关系应被自动清理以释放资源。栈区恰好符合这种对变量生命周期进行严格管理的需求。
# 每当函数调用发生时,会在栈顶为该函数分配一块空间,用于存放其内部声明的变量及其关联关系;函数调用结束时,这部分栈空间会被自动回收
# # 3. 性能优化
# 栈区访问速度极快,因为它的内存地址通常连续且靠近CPU寄存器。将变量名与值内存地址的关联关系放在栈上,可以确保编译器和运行时系统能以较低的时间成本查找并操作这些信息。特别是在递归调用、循环等频繁涉及变量访问的场景下,栈区的高效访问对于提升程序整体性能至关重要
# # 4. 内存安全性
# 栈区的内存由编译器自动分配和释放,不易出现内存泄漏问题。将变量名与值内存地址的关联关系存储在栈上,有助于防止因程序员手动管理内存不当而导致的错误。此外,栈区的大小通常有限制,这有助于防止因局部变量过度使用内存而导致的堆栈溢出问题,从而提高程序稳定性
# # 5. 线程安全性
# 每个线程都有自己的栈空间,这意味着不同线程中的同名局部变量各自拥有 ** 的内存地址和关联关系,互不影响。这一特性保证了并发环境下的数据安全,避免了线程间的数据混淆。
# # 综上所述
# 将变量名与值内存地址的关联关系存放于栈区,既利用了栈区的特性实现了高效的内存管理、快速访问和严格的生命周期控制,又确保了内存安全性与线程安全性,有利于编写稳定、高效且易于维护的程序代码。
# 当然,需要注意的是,上述讨论主要针对局部变量。
# 对于静态变量、全局变量或动态分配的对象,它们的内存地址关联关系可能存在于数据段(Data Segment)或堆区(Heap),而非栈区。
字符编码
1. 数据存放位置
我们所学的所有数据都存放在内存中
如何将数据存到硬盘中就是我们要学的
2. 字符编码介绍
计算机只能识别的只有二进制数据,二进制就是0/1,我们所要学习的就是将我们的文字转换成计算机可以识别的文字。
3. 如何进行编码
【1】解码—>将二进制数据转换成字符
data = b'\xe5\xa5\x87\xe7\x89\xb9'
print(data.decode('utf-8'))
# 奇特
name = b'silence'
print(type(name))
# <class 'bytes'>
【2】编码—>将字符转换为二进制数据
name = '奇特'
print(name.encode('utf-8'))
# b'\xe5\xa5\x87\xe7\x89\xb9'
将内存中的数据持久化保存到文件中
1. 打开文件的方式
【1】使用open语句
三种模式:a:追加写 w:覆盖写 r:读
参数是(文件路径,打开文件的模式,编辑格式)
fp = open ('01.txt', 'r', encoding='utf-8')
print(fp)
# FileNotFoundError: [Errno 2] No such file or directory: '01.text'
读取所有文件数据.read
fp = open ('01.txt', 'r', encoding='utf-8')
data = fp.read()
print(data) #silence
打印
print(data)
fp.close()
【2】with语句
在with语句内部打开文件后会自动执行close()把文件关闭—->节省资源
with open('01.txt', 'r', encoding='utf-8') as fp:
data = fp.read()
print(data) # silence
2.对文件的操作模式
【1】读模式 r
# 01.txt文件中存的数据是silence|741
with open('01.txt', 'r', encoding='utf-8') as fp:
data = fp.read()
#def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True): 默认'r'处是只读
username = data.split('|')
print(username) #['silence', '741\n']
已经学过的字符串格式化语法
f:格式化输出
b:将字符串转为二进制
r:取消\转义
print('a\\nb') # a\nb
print(r'a\nb') # a\nb
【2】覆盖写模式 w
覆盖写:文件名存在则打开,不存在则新建
打开当前文件并且将文件内容清空然后写入新内容
#(1)一次写入
data = 'happy|123'
with open(file='01.txt', mode ='w',encoding='utf-8') as fp:
fp.write(data) #happy|123
#01.txt文件中原来存的数据是silence|741
#(2)连续写入
data = 'happy|123'
with open(file='01.txt', mode ='w',encoding='utf-8') as fp:
fp.write(data) #happy|123
fp.write('爱吃排骨') # happy|123爱吃排骨
【3】追加写模式 a
特点: 文件不存在则新建并存入
文件存在则打开并继续写入
不自带换行效果 可以 +'\n'
data ='silence|741'
with open('01.txt', 'a', encoding='utf-8') as fp:
fp.write(data) #happy|123爱吃排骨silence|741silence|741
# 运行几次就写入几次
3.文本操作模式补充
【1】文本编辑模式
w / a/ r
完整模式应该是 wt/ at/ rt 大文本编辑模式
【2】二进制数据模式
wb / rb
with open ('img.jpg', 'rb')as fp:
data = fp.read()
print(data) #...\x03\x03\x89xl\xde\xeap\x88[\xb6\x19\x1b'读取出一堆二进制数据
with open('girl.jpg', 'wb')as fp:
fp.write(data)
# 读取原来照片数据 将其生成在别的
4 文件操作扩展模式
r+ / w+/ a+
r:只读 w: 只写无法读
with open('01.txt', 'r+', encoding='utf_8')as fp:
fp.write('888') # 888py|123
with open('01.txt', 'r+', encoding='utf_8')as fp:
fp.write('888')
# 写完以后需要永久化保存才能读出来,否则是空的
data = fp.read()
print(data) # py|123
5 文件的操作方法详细
data = 'silence|741'
with open('01.txt', 'a', encoding='utf_8')as fp:
fp.write(data + '\n')
#silence|741
#silence|741
#silence|741
#silence|741
【1】r模式的方法详细
with open('01.txt', 'r', encoding='utf_8')as fp:
# (1)一次性全部读完
data = fp.read()
print(data)
with open('01.txt', 'r', encoding='utf_8')as fp:
# (2)每次只读一行
data = fp.readline()
print(data)
# 循环单次读出
with open('01.txt', 'r', encoding='utf_8')as fp:
counter = 0
while True:
counter += 1
data = fp.readline()
if data:
print(counter,data)
else:
break
# 1 silence|741
# 2 silence|741
# 3 silence|741
# 4 silence|741
# (3)一次性读很多行--->把所有数据读出来后放到一个列表
with open('01.txt', 'r', encoding='utf_8')as fp:
data = fp.readlines()
print(data)
# ['silence|741\n', 'silence|741\n', 'silence|741\n', 'silence|741\n']
# (4)测试当前对象是否可读
print(fp.readable())
【2】w模式的操作方法
with open('01.txt', 'w', encoding='utf_8')as fp:
# (1)将原本内容清空并一次性写入新内容
fp.write('666')
data_list = ['111', '\n' , '222']
with open('01.txt', 'w', encoding='utf_8')as fp:
fp.writelines(data_list)
# 111
# 222
7.控制文件内的指针移动
(1).read(数字)
#文件里放的是123456789
with open('01.txt', 'r', encoding='utf_8')as fp:
# read 可以放参数,参数是读取到哪个索引位置
data = fp.read(4)
print(data) # 1234
(2)seek函数
f.seek(指针移动的字节数,模式控制) 一个中文三个字节
0:默认模式,该模式指针是文件开头开始写
1:该模式是以指针所在位置开始写
2:该模式是在末尾开始写
#(1) 0模式
with open('01.txt', 'r', encoding='utf_8')as fp:
# 以开头作为参照向后移动3个字符
fp.seek(3,0)
# 查看指针所在的索引位置
print(fp.tell()) # 3
print(fp.read()) # 456789
#(2)1模式:以当前位置作为参照向后移动
# 在python 3.x版本后不允许在文本文件中使用
#只能在二进制中使用
with open('img.jpg', 'rb' )as fp:
# 以当前位置作为参照向后移动字符
fp.seek(3,1)
print(fp.tell())
print(fp.read())
#(3)2模式:以结尾位置作为参照移动(从后向前移动)
with open('img.jpg', 'rb' )as fp:
fp.seek(-2,2)
print(fp.tell())
print(fp.read()) # b'\x19\x1b'
小练习
'''
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
'''
"""
张一蛋<妇女主任> 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
"""
with open('01.txt', 'r+', encoding='utf-8') as sl:
sl.seek(9, 0)
sl.write('<妇女主任>')
sl.seek(0, 0)
data = sl.read()
print(data)
标签:fp,编码,字符,open,list,num,GC,print,data
From: https://www.cnblogs.com/zyb123/p/18122425