【一】深浅拷贝
【1】深浅拷贝问题
- 如果是浅拷贝,只会复制一层,如果拷贝的对象中有可变数据类型,修改可变数据类型还会影响拷贝的对象
- 如果是深拷贝,完整复制,无论可变或不可变,都是创建出新的来,无法怎么修改原对象,都不会对拷贝出的对象造成影响
(1)浅拷贝
- 对于原对象中的可变数据类型进行修改操作时,拷贝的对象也会受到影响
num_list = [1, 2, 3, 4, 5]
copy_num_list = num_list
copy_num_list.append(6)
print(num_list) #[1, 2, 3, 4, 5, 6]
print(copy_num_list) #[1, 2, 3, 4, 5, 6]
#输出结果可看到修改了复制后列表的元素后,原列表的元素也跟着改变了
#因为浅拷贝仅复制了列表的第一层,而对于内部的子列表,拷贝对象和原对象共享同一个引用
(2)深拷贝
- 深拷贝会创建一个独立的新对象,不受原对象的影响(无论原对象的数据类型是可变还是不可变)
import copy
num_list = [1, 2, 3, 4, 5]
copy_num_list = copy.deepcopy(num_list)
num_list.append(6)
print(num_list) #[1, 2, 3, 4, 5, 6]
print(copy_num_list) #[1, 2, 3, 4, 5]
#从输出结果可以看出修改了原列表,复制后的列表不会因为原列表被修改了而跟着被修改了,保持原来的样子
#当对象中含有可变数字类型,原则依旧遵循
import copy
num_list = [1, 2, 3, 4, 5, [1, 2]]
copy_num_list = copy.deepcopy(num_list)
num_list[5].append(6)
print(num_list) #[1, 2, 3, 4, 5, [1, 2, 6]]
print(copy_num_list) #[1, 2, 3, 4, 5, [1, 2]]
【二】小结
- 浅拷贝
- 浅拷贝只复制顶层对象,不影响深层对象
- 它对新对象的修改会影响复制的对象
- 深拷贝
- 深拷贝会递归复制整个对象结构
- 它能确保对复制对象的修改不会影响原对象。
【二】垃圾回收机制(GC机制)
【1】什么是垃圾回收机制
- 垃圾回收机制(GC机制)是Python自带的一种机制
- 专门用来回收不可用的变量值所占用的内存空间
【2】为什么要有垃圾回收机制
- 程序运行过程中会申请大量的内存空间,而对于一些无用的内存空间如果不及时清理的话会导致内存爆满,导致程序崩溃
- 因此管理内存是一件重要且繁杂的事情,而python自带的垃圾回收机制可以帮助我们解决这个问题
【3】GC原理
(1)栈区
- 变量名与值内存地址的关联关系通常被存放于程序的栈区
# 1. 栈区的特性与作用
# 栈(Stack)是计算机内存中的一种数据结构,其主要特点是后进先出(LIFO)。在编程语言执行过程中,栈区用于存储局部变量、函数参数以及函数调用时的返回地址等临时性信息。栈区的操作快速且高效,因为它遵循简单的压栈(push)和弹栈(pop)规则,适合处理那些生命周期短、使用频繁且需要保持特定顺序的数据。
#
# 2. 变量生命周期管理
# 变量名与值内存地址的关联关系本质上是一种符号表信息,它记录了源代码中定义的变量名与其所对应的内存地址之间的映射。这种信息具有明显的生命周期特征:当进入一个作用域(如函数或代码块)时,新声明的变量及其关联关系应被创建;当离开该作用域时,这些变量及关联关系应被自动清理以释放资源。栈区恰好符合这种对变量生命周期进行严格管理的需求。每当函数调用发生时,会在栈顶为该函数分配一块空间,用于存放其内部声明的变量及其关联关系;函数调用结束时,这部分栈空间会被自动回收。
#
# 3. 性能优化
# 栈区访问速度极快,因为它的内存地址通常连续且靠近CPU寄存器。将变量名与值内存地址的关联关系放在栈上,可以确保编译器和运行时系统能以较低的时间成本查找并操作这些信息。特别是在递归调用、循环等频繁涉及变量访问的场景下,栈区的高效访问对于提升程序整体性能至关重要。
#
# 4. 内存安全性
# 栈区的内存由编译器自动分配和释放,不易出现内存泄漏问题。将变量名与值内存地址的关联关系存储在栈上,有助于防止因程序员手动管理内存不当而导致的错误。此外,栈区的大小通常有限制,这有助于防止因局部变量过度使用内存而导致的堆栈溢出问题,从而提高程序稳定性。
#
# 5. 线程安全性
# 每个线程都有自己的栈空间,这意味着不同线程中的同名局部变量各自拥有 ** 的内存地址和关联关系,互不影响。这一特性保证了并发环境下的数据安全,避免了线程间的数据混淆。
#
# 综上所述
# 将变量名与值内存地址的关联关系存放于栈区,既利用了栈区的特性实现了高效的内存管理、快速访问和严格的生命周期控制,又确保了内存安全性与线程安全性,有利于编写稳定、高效且易于维护的程序代码。
# 当然,需要注意的是,上述讨论主要针对局部变量。
# 对于静态变量、全局变量或动态分配的对象,它们的内存地址关联关系可能存在于数据段(Data Segment)或堆区(Heap),而非栈区。
(2)堆区
- 变量值存放于堆区,内存管理回收的则是堆区的内容
# 在Python中,变量和它们所引用的对象(如数值、字符串、列表、字典等)的存储位置与内存管理机制密切相关。
# 这里提到的“变量值存放于堆区”主要涉及两个概念:变量本身和它所引用的对象。
# 1. 变量
# 变量是程序中用于标识数据的一个符号或名字,它并不直接存储数据,而是指向(或引用)实际存储数据的内存地址。
# 在Python解释器内部,变量名通常保存在栈(Stack)中。栈是一种后进先出(LIFO)的数据结构,用于高效地管理函数调用时的局部变量、返回地址等信息。
# 当创建一个变量时,Python会在当前作用域(如全局作用域或函数局部作用域)的栈空间内为该变量分配一个名称,并将其关联到相应的对象。
#
# 2. 对象及值
# Python中的对象(即变量所引用的实际数据)通常存储在堆(Heap)中。
# 堆是一种动态分配内存的区域,相比于栈,其大小更为灵活且无固定上限。
# 堆用于存储需要大量、不固定大小内存空间的数据结构,如大数组、复杂的数据类型(如类实例)、以及那些生命周期可能跨越多个函数调用或作用域的对象。
#
# 将对象值存放在堆区有以下几个原因:
#
# 灵活性
# 堆内存的大小可以动态调整,能够适应不同大小和复杂度的对象。
# 这使得Python可以在运行时根据需要创建任意大小的对象,无需预先确定其具体尺寸,非常适合处理变长数据结构(如列表、字典)。
#
# 共享性
# 多个变量可以同时引用同一块堆内存中的对象,实现对象的共享。
# 这种机制允许通过赋值、参数传递、返回值等方式高效地复制“引用”,而不是复制整个对象内容,节省了内存资源并提高了效率。
# 垃圾回收
# 由于堆内存中的对象可能存在多个引用,Python使用垃圾回收(Garbage Collection, GC)机制自动追踪和管理对象的生命周期。
# 当一个对象不再有任何引用时,GC会识别并释放其占用的堆内存,防止内存泄漏。
# 堆区便于实施这样的垃圾回收策略,确保程序长期运行时内存的有效利用。
#
# 综上所述
# Python将变量值(即对象)存放于堆区,主要是为了提供灵活的内存分配、支持对象共享以及实现自动化的垃圾回收,这些特性对于编写高效、健壮的Python程序至关重要。
# 而变量本身作为对象的引用,则通常存储在栈区,便于快速访问和管理其生命周期。
【4】垃圾回收机制原理
- 首先我们对记住计数为主,标记清除、分代回收为辅这句话
(1)引用计数
(1)引用计数
- 引用计数就是:变量值被变量名关联的次数
- 如:age=18
- 变量值18被关联了一个变量名age,称之为引用计数为1
(2)引用计数增加
- age=18 (此时,变量值18的引用计数为1)
- m=age (把age的内存地址给了m,此时,m,age都关联了18,所以变量值18的引用计数为2)
(3)引用计数减少
- age=10(名字age先与值18解除关联,再与3建立了关联,变量值18的引用计数为1)
- del m(del的意思是解除变量名x与变量值18的关联关系,此时,变量18的引用计数为0)
(4)总结
- 一个变量值可以有多个变量名指向,有一个变量名指向,引用计数就是1,有两个就是2
- 只要引用计数不为0,这个变量值就不是垃圾数据,当引用计数为0了,说明这个变量值就没用了,就可以清除了
(2)标记清除
- 在Python的内存管理中,标记-清除算法是一种常用的垃圾回收(Garbage Collection)方法之一。它的主要目标是解决循环引用导致的内存泄漏问题。
- 通俗理解为当内存空间即将沾满的时候,python会暂停程序的运行,从头到位扫描一遍,并且把扫描出来的垃圾数据做标记,然后,一次性做清除处理
- 容器对象(比如:list,set,dict,class,instance)都可以包含对其他对象的引用,所以都可能产生循环引用。
- 而“标记-清除”计数就是为了解决循环引用的问题。
- 标记/清除算法的做法是当应用程序可用的内存空间被耗尽的时,就会停止整个程序,然后进行两项工作
(3)分代回收
(1)分代回收的核心思想
- 分代回收是一种垃圾回收的策略,其核心思想是根据对象的存活时间将其划分为不同的代
- 在Python中,通常将内存分为三代:
- 新生代: 包含了大部分新创建的对象,其存活时间较短。
- 青春代: 包含了已经经历过垃圾回收一次的新生代对象,存活时间较长。
- 老年代:包含了已经经历过多次垃圾回收的对象,存活时间较长。
(2)具体实现原理
- 大部分对象在其刚被创建的时候往往很快变得不可达,即它们的生命周期较短。
- 部分对象在经历了一次垃圾回收后,仍然存活的概率较高,即它们的生命周期较长。
- 因此,分代回收将内存划分为不同的代,并根据代的特点采用不同的垃圾回收策略。
- 新生代中的对象:采用较为频繁的垃圾回收,通常使用标记-复制算法,即标记存活对象,然后将存活对象复制到另一块内存中,最后清理掉原有的内存。这能有效处理新生代中大部分对象的垃圾回收。
- 青春代中的对象:采用标记-清除算法,其目标是处理在新生代中经历了一次回收的对象。由于这些对象的生命周期较长,不需要频繁地进行回收。
- 老年代中的对象:采用标记-清除算法或标记-整理算法。老年代的对象生命周期更长,采用相对稳定的垃圾回收策略。
(3)回收
- 分代回收机制对不同代的垃圾回收频率进行了调整:
- 新生代的垃圾回收比较频繁,因为大部分对象在新生代中被创建后很快就变得不可达。
- 青春代的垃圾回收频率较低,因为这些对象已经经历了一次回收,存活概率相对较高。
- 老年代的垃圾回收频率较低,因为老年代的对象生命周期较长。
【5】小整数池
- python中经常使用的一些数值定义为小整数池,小整数池的范围是[-5,256],python对这些数值已经提前创建好了内存空间,即使多次重新定义也不会在重新创建新的空间,但是小整数池外的数值在重新定义时都会再次创建新的空间。
- 所以对于小整数池中的数,内存地址一定是相同的,小整数池中外的数,内存地址是不同的。
a = 10
b = 10
print(a is b)
# True
a = 257
b = 257
print(a is b)
# False
【三】字符编码
【1】数据存放位置
- 所有软件都是运行硬件之上的
- 与运行软件相关的三大核心硬件为cpu、内存、硬盘
- 软件运行前,软件的代码及其相关数据都是存放于硬盘中的
- 任何软件的启动都是将数据从硬盘中读入内存,然后cpu从内存中取出指令并执行
- 软件运行过程中产生的数据最先都是存放于内存中的,若想永久保存软件产生的数据,则需要将数据由内存写入硬盘
【2】什么是字符编码
-
人类在与计算机交互时,用的都是人类能读懂的字符,如中文字符、英文字符、日文字符等,但是计算机只能识别二进制数
-
翻译的过程必须参照字符编码表,该表上存放的就是字符与数字一一对应的关系。
-
字符编码中的编码指的是翻译或者转换的意思,就是将人能理解的字符翻译成计算机能识别的数字
【3】字符编码的发展
【1】阶段一:一家独大(ASCII)
-
现代计算机起源于美国,所以最先考虑仅仅是让计算机识别英文字符,于是诞生了ASCII表
-
ASCII表的特点:
- 只有英文字符与数字的一一对应关系
- 一个英文字符对应1Bytes,1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
-
字母对应ASCII表(小写字母对应的数字一定大于大写字母):
- A-Z:65-90
- a-z:97-122
- 0-9:48-57
【2】阶段二:诸侯割据(ASCII/GBK/Shift_JIS)
-
为了让计算机能够识别中文和英文,中国人定制了 GBK
-
GBK表的特点:
- 只有中文字符、英文字符与数字的一一对应关系
- 一个英文字符对应1Bytes 一个中文字符对应2Bytes
- 1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
- 2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符
-
各国不同的编码表
- 为了让计算机能够识别日文字符、英文字符与数字的一一对应关系,日本制定了 Shift_JIS 表
- 为了让计算机能够识别韩文字符、英文字符与数字的一一对应关系,韩国制定了 Euc-kr表
- 美国人用的是ASCII,中国人用的是GBK,日本人用的是Shift_JIS
-
多种编码共存的缺点:
- 在美国人用的计算机里只能输入英文字符
- 而在中国人用的计算机里只能输入中文字符和英文字符
- 毫无疑问我们希望计算机允许我们输入万国字符均可识别、不乱码,而现阶段计算机采用的字符编码ASCII、GBK、Shift_JIS都无法识别万国字符
【3】阶段三:一统天下(unicode)
-
unicode于1990年开始研发,1994年正式公布
-
unicode的特点:
- 存在所有语言中的所有字符与数字的一一对应关系,即兼容万国字符
- 软件是存放于硬盘的,而运行软件是要将软件加载到内存的
-
字符的存储:
- 文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的,存放于硬盘中,则可以转换成任意其他编码,只要该编码可以支持相应的字符
- 英文字符可以被ASCII识别:英文字符--->unciode格式的数字--->ASCII格式的数字
- 中文字符、英文字符可以被GBK识别:中文字符、英文字符--->unicode格式的数字--->gbk格式的数字
- 日文字符、英文字符可以被shift-JIS识别:日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字
【4】如何编解码
(1)编码(encode)
- 由字符转换成内存中的unicode,以及由unicode转换成其他编码的过程,都称为编码encode
x = '上' # 在python3在'上'被存成unicode
res = x.encode('utf-8')
print(res, type(res)) # unicode编码成了utf-8格式,而编码的结果为bytes类型,可以当作直接当作二进制去使用
# b'\xe4\xb8\x8a' <class 'bytes'>
# 只有英文字符和数字,要想编码的话,直接使用前缀b --- 字节对象没有encode方法
s = b'ligo123'
print(s, type(s))
# b'ligo123' <class 'bytes'>
(2)解码(decode)
- 由内存中的unicode转换成字符,以及由其他编码转换成unicode的过程,都称为解码decode
- 在诸多文件类型中,只有文本文件的内存是由字符组成的,使用也关乎到字符编码的问题
x = b'\xe4\xb8\x8a'
res = x.decode('utf-8')
print(res, type(res))
# 上 <class 'str'>
s = b'ligo123'
res = s.decode('utf-8')
print(res, type(res))
# ligo123 <class 'str'>
【5】utf-8的诞生
- 如果保存到硬盘的是GBK格式二进制,当初用户输入的字符只能是中文或英文
- 同理如果保存到硬盘的是Shift_JIS格式二进制,当初用户输入的字符只能是日文或英文……
- 如果我们输入的字符中包含多国字符,那么该如何处理?
- 多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(GBK格式的二进制)
- 多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(Shift_JIS格式的二进制)
- 多国字符—√—>内存(unicode格式的二进制)——√—>硬盘(???格式的二进制)
(1)解决办法(utf-8)
- 多国字符—√—>内存(unicode格式的二进制)——√—>硬盘(utf-8格式的二进制)
(2)为何在内存中不直接使用utf-8
- utf-8是针对Unicode的可变长度字符编码:
- 一个英文字符占1Bytes,
- 一个中文字符占3Bytes,生僻字用更多的Bytes存储
- unicode更像是一个过渡版本,
- 我们新开发的软件或文件存入硬盘都采用utf-8格式
【6】字符编码的应用
(1)学习字符编码的目的
- 学习字符编码就是为了存取字符时不发生乱码问题
- 内存中固定使用unicode无论输入任何字符都不会发生乱码
- 我们能够修改的是存/取硬盘的编码方式,如果编码设置不正确将会出现乱码问题。
(2)乱码问题
-
乱码问题分为两种:存乱了,读乱了
-
存乱了:如果用户输入的内容中包含中文和日文字符,如果单纯以shift_JIS存,日文可以正常写入硬盘,而由于中文字符在shift_jis中没有找到对应关系而导致存乱了
-
读乱了:如果硬盘中的数据是shift_JIS格式存储的,采GBK格式读入内存就读乱了
(3)总结
-
在由内存写入硬盘时,必须将编码格式设置为支持所输入字符的编码格式
-
在由硬盘读入内存时,必须采用与写入硬盘时相同的编码格式
【四】文件操作
【1】打开文件的两种方式
(1)open语句
# 三种模式 : w 覆盖写 a 追加写 r 读
#1.打开文件
fp = open('01.text', 'r', encoding='utf-8')
#2.读取文件数据
data = fp.read()
#3.打印
print(data)
#关闭文件
fp.close()
(2)with open语句
#【1】回收资源
f.close() # 回收操作系统打开的文件资源
del f # 回收应用程序级的变量
#del f一定要发生在f.close()之后
#【2】with关键字
# 1.open(文件路径/文件名, 打开模式,编码方式)
with open('01.text', 'r', encoding='utf8') as fp:
data = fp.read()
print(data)
# 2.可用with同时打开多个文件,用逗号分隔开即可
with open('01.text', 'r',encoding='utf8') as fp1, open('02.text', 'w', encoding='utf8') as fp2:
data = fp1.read()
fp2.write(data)
(3)指定操作文本文件的字符编码
- 如果打开的是文本文件,会涉及到字符编码问题
- 没有给open指定编码,文件就会根据系统去使用默认编码
fp = open('01.text', 'r', encoding='utf-8')
【2】文件的操作模式
(1)文件操作相关参数
r: # 以只读方式打开文件。文件的指针将会放在文件的开头。这是**默认模式**。
rb: # 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
r+: # 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+: # 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w: # 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb: # 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
w+: # 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb+: # 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
a: # 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab: # 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+: # 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+: # 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
(2)读模式(r)
- 在文件不存在时则报错,文件存在文件内指针直接跳到文件开头
with open('01.text', mode='r', encoding='utf-8') as fp:
data = fp.read()
#字符转义问题
# 将文件绝对路径做成字符串格式时,由于 \ 在Python中是转义符,因此和不用的字符会产生不同的转义效果
path = 'E:\PythonProjects\01.py' # 错误
# 修正方式一 : \\ 双转义,将原来的 \ 进行反转义
path = 'E:\\PythonProjects\\01.py' # \\ 反转义
# 修正方式二 : 在带 \ 的路径字符串前 + r 进行取消转义
path = r'E:\PythonProjects\01.py' # \\ +r 取消转义
(3)覆盖写模式(w)
- 在文件不存在时会创建空文档,文件存在会清空文件,文件指针跑到文件开头
with open('01.text', mode='w', encoding='utf-8') as fp:
fp.write("123")
fp.write("456")
#在文件不关闭的情况下,连续的写入,后写的内容一定跟在前写内容的后面
#这种情况再文件里显示123456
#如果重新以w模式打开文件,则会清空文件内容
with open('01.text', mode='w', encoding='utf-8') as fp:
fp.write("123")
with open('01.text', mode='w', encoding='utf-8') as fp:
fp.write("456")#
#这样文件里只有456
(4)追加写模式(a)
- 在文件不存在时会创建空文档,文件存在会将文件指针直接移动到文件末尾
#w模式与a模式的异同
#相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前写的内容之后
#不同点:以a模式重新打开文件,不会清空原文件内容,会将文件指针直接移动到文件末尾,新写的内容永远写在最后
with open('01.text', mode='a', encoding='utf-8') as fp:
fp.write("123")
fp.write("456")
#注册功能练习
username = input('请输入用户名: ').strip()
password= input('请输入密码: ').strip()
with open('01.text', mode='a', encoding='utf-8') as fp:
data = '%s:%s\n' %(username,password)
fp.write(data)
【3】控制文件读写内容的模式
- tb模式不能单独使用,必须与r/w/a之一结合使用
(1)t 模式的使用
- 读写文件都是以字符串为单位的,只能针对文本文件,必须指定encoding参数
#如果我们指定的文件打开模式为r/w/a,其实默认就是rt/wt/at
name_list = ['ligo', 'scott']
with open('01.text', 'w', encoding='utf8') as fp:
# 无法直接写入列表、字典等类型,只能写入字符串格式的内容,所以要用str强转
fp.write(str(name_list))
#t 模式只能用于操作文本文件,无论读写,都应该以字符串为单位
(2)b模式的使用
- 读写文件都是以二进制为单位的,可以针对所有文件,一定不能指定encoding参数
with open('01.jpg',mode='rb') as fp:
data=fp.read()
print(type(data)) # 输出结果为:<class 'bytes'>
with open('01.text',mode='wb') as fp:
msg="好好好"
res=msg.encode('utf-8')
fp.write(res) # 在b模式下写入文件的只能是bytes类型
#针对非文本文件(如图片、视频、音频等)只能使用b模式
【4】文件操作方法
(1)读操作
#f.read():读取所有内容,执行完该操作后,文件指针会移动到文件末尾
#f.readline():读取一行内容,光标移动到第二行首部
#f.readlines():读取每一行内容,存放于列表中
#一次性读入内容
#【1】
with open('copy1.txt', mode='r', encoding='utf-8') as f:
for line in f:
print(line)
# good
#
# bad
#
# very good
#【2】readlines()读取每一行
with open('copy1.txt', mode='r', encoding='utf-8') as fp:
data = fp.readlines()
print(data)
# ['good\n', 'bad\n', 'very good']
#【3】read()读取所有内容
with open('copy1.txt', mode='r', encoding='utf-8') as fp:
data = fp.read()
print(data)
# good
# bad
# very good
#【4】readline()读取一行
with open('copy1.txt', mode='r', encoding='utf-8') as fp:
data = fp.readline()
print(data)
# good
(2)写操作
- f.write():一次性全部写入
#先读取该文件里的内容,在覆盖写入,原本的内容变成新的内容
with open('copy1.txt', 'r', encoding='utf8') as fp1:
data = fp1.read()
print(data) # 今天是个好日子!
with open('copy1.txt', 'w', encoding='utf8') as fp2:
fp2.write('good') # good
- f.writelines():可以迭代写入可迭代对象内的内容
#用\n对文件内容进行换行,否则会连着写入
with open('copy1.txt', 'r', encoding='utf8') as fp1:
data = fp1.read()
print(data) #输出 今天是个好日子!
with open('copy1.txt', 'w', encoding='utf8') as fp2:
fp2.writelines('good' + '\n' + 'bad' + '\n' + 'very good')
# good
# bad
# very good
(3)判断文件是否可读/写/关闭
#f.readable():文件是否可读
#f.writable():文件是否可写
#f.closed:文件是否关闭
#f.encoding:如果文件打开模式为b,则没有该属性
#f.flush():立刻将文件内容从内存刷到硬盘
#f.name():获取文件的名
with open('01.txt', mode='w', encoding='utf-8') as f:
print("{f.readable()}") # 输出 False
print("{f.writable()}") # 输出 True
print("{f.closed}") # 输出 False
print("{f.encoding}") # 输出 utf-8
f.flush()
print(f"在 'with' 块之后是否关闭: {f.closed}") # 输出 在 'with' 块之后是否关闭: True
【5】控制文件指针移动
- 文件内指针的移动都是Bytes为单位的,唯一例外的是t模式下的read(n),n以字符为单位
(1)语法
# 文件内容 abcdefg
with open('01.text', mode='r', encoding='utf-8') as fp:
data = fp.read(3) # 读取3个字符
print(data) # abc
with open('01.text', mode='rb') as fp:
data1 = fp.read(3) # 读取3个字节
data2 = fp.read(5)
print(data1) # b'abc'
print(data2) # b'defg\r'
(2)seek移动指针
-
读取文件某一特定位置的数据,需要用f.seek方法主动控制文件内指针的移动
-
f.seek(指针移动的字节数,模式控制)
-
模式控制:
- 0模式代表指针移动的字节数是以文件开头为参照的
- 1模式代表指针移动的字节数是以当前所在的位置为参照的
- 2模式代表指针移动的字节数是以文件末尾的位置为参照的
-
其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用
#【1】0模式
# 文件内容 abcdefg
with open('01.text', mode='r', encoding='utf-8') as fp:
fp.seek(2, 0)
# 查看当前文件指针距离文件开头的位置,输出 2
print(fp.tell()) # 2
# 从第3个字节的位置读到文件末尾 ,输出 cdefg
print(fp.read()) # cdefg
with open('01.text', mode='rb') as fp:
fp.seek(4, 0)
# print(fp.read()) # b'efg\r\n'
print(fp.read().decode('utf-8')) # efg
#【2】1模式
# 文件内容 abcdefg
with open('01.text', mode='rb') as fp:
fp.seek(4, 1)
# 从当前位置往后移动4个字节,而此时的当前位置就是文件开头
print(fp.tell()) # 4
fp.seek(5, 1)
# 从当前位置往后移动4个字节,而此时的当前位置为4
print(fp.tell()) # 9
#【3】2模式
# 文件内容 abcdefg
with open('01.text', mode='rb') as fp:
fp.seek(0, 2)
# 参照文件末尾移动0个字节,即直接跳到文件末尾
print(fp.tell()) # 9
fp.seek(-3, 2)
# 参照文件末尾往前移动了3个字节
print(fp.tell()) # 6
【六】seek函数补充
-
seek函数用于移动文件中指针位置和指定指针移动偏移量大小
-
seek函数格式
fp.seek(偏移量x,参数)
- 偏移量可正可负,正数向后移动x位,负数从后向前移动x位
- 参数有0、1、2三个,0是将指针移动到文件开头,1是将指针移动到当前位置,2是将指针移动到文件结尾
with open('copy1.txt', mode='r', encoding='utf-8') as fp:
data = fp.read()
print(data)
fp.seek(3, 0)
print(fp.read())
# good bad very good
# d bad very good
【七】练习
(1)seek指针替换
'''
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
'''
with open('01.text', 'r+', encoding='utf8') as fp:
fp.seek(9)
fp.write('<妇女主任>')
fp.seek(0,0)
data = fp.read()
print(data)
#问题所在:如果继续写入的话会接着后面继续写入
#张一蛋<妇女主任> 179 49 12344234523
#李二蛋 河北 163 57 13913453521
#王全蛋 山西 153 62 18651433422
(2)完整替换
'''
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
'''
with open('01.text', 'r', encoding='utf8') as fp:
lines = fp.readlines()
line_list = []
for line in lines:
if '张一蛋' in line:
line = line.replace(line.split()[0], f"{line.split()[0]}(妇女主任)")
elif '李二蛋' in line:
line = line.replace(line.split()[0], f"{line.split()[0]}(村长)")
elif '王全蛋' in line:
line = line.replace(line.split()[0], f"{line.split()[0]}(书记)")
line_list.append(line)
print(line_list)
#张一蛋(妇女主任) 山东 179 49 12344234523
#李二蛋(村长) 河北 163 57 13913453521
#王全蛋(书记) 山西 153 62 18651433422
(3)替换内容思路
#【1】将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件
# 优点: 在文件修改过程中同一份数据只有一份
# 缺点: 会过多地占用内存
with open('01.text',mode='r',encoding='utf8') as fp:
data=fp.read()
with open('01.text',mode='w',encoding='utf8') as fp:
fp.write(data.replace('a','b'))
# 原文件内容 a = 1
# 修改后 b = 1
#【2】以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件...,删掉原文件,将临时文件重命名原文件名
# 优点: 不会占用过多的内存
# 缺点: 在文件修改过程中同一份数据存了两份
import os
with open('01.text', mode='r', encoding='utf8') as fp1, open('02.text', mode='w', encoding='utf8') as fp2:
for line in fp1:
fp2.write(line.replace('a', 'b'))
os.remove('01.text')
os.rename('02.text', '01.text')
# 原文件内容 a = 1
# 修改后 b = 1
标签:fp,编码,字符,Python,GC,文件,print,open,内存
From: https://www.cnblogs.com/ligo6/p/18179974