【一】深浅拷贝
【1】深浅拷贝问题
- 无论深拷贝还是浅拷贝都是用来 复制对象的
- 如果是浅copy,只会复制一层,如果copy的对象中有可变数据类型,修改可变数据类型还会影响拷贝的对象
- 如果是深copy,完整复制,无论可变或不可变,都是创建出新的来,以后再改原对象,都不会对copy出的对象造成影响
# 给你一个列表 ---> 基于这个列表进行更改生成新的列表
# 方式一:将原本的列表复制一份一模一样的
# 支队新列表生效,对原来的列表不生效
# num_list = [1, 2, 3]
# num_list_new = [1, 2, 3]
# num_list_new.append(4)
# print(num_list)
# print(num_list_new)
[1, 2, 3]
[1, 2, 3, 4]
# 方式二:用新的变量名指向原来的列表 ---> 连续复制
# 对新列表和原来的列表都生效
num_list = [1, 2, 3]
num_list_new = num_list
num_list_new.append(4)
print(num_list_new)
print(num_list)
[1, 2, 3, 4]
[1, 2, 3, 4]
【2】复制原本的列表会产生的问题
# 新列表是复制出来的副本
# 【1】修改新列表会影响到原本的列表 ----> 浅拷贝
# 【2】修改新列表不会影响到原本的列表 ---> 深拷贝
【3】在python代码中实现深浅拷贝
# 【1】浅拷贝
# 必须是修改源数据类型中的可变数据类型才生效
# 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))
# print(num_list_new, id(num_list_new))
# [1, 2, 3, 4, [1, 2, 5]] 2504131502208
# [1, 2, 3, 4, [1, 2, 5]] 2504131490752
# 【2】深拷贝
# 深拷贝会将原来的列表完全复制一份,修改新列表不会影响到原来的列表
# 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))
# print(num_list_new, id(num_list_new))
[1, 2, 3, 4, [1, 2]] 1819253367104
[1, 2, 3, 4, [1, 2, 5]] 1819253366272
【总结】
# 浅拷贝只会复制顶层对象,而不会影响到深层的可变数据类型
# 在复制出来的列表,列表中的列表引用的是原来列表的引用
# 深拷贝会递归的复制整个对象的数据结构
# 在复制出来的列表,列表中的列表引用的是新列表的引用
【二】垃圾回收处理机制(GC机制)
【1】什么是垃圾回收处理机制
# 垃圾回收机制是(GC机制)python自带的一种机制
# 专门用来回收变量值所占的内存空间
# 每节课都写了一个 py 文件
# 180添加 --》 180个文件(至少) ---> 有的文件只是临时测试的文件
# 一般不会主动删除不必要的文件 ---> 文件越堆越多 --> 硬盘中的空间也会越战越多
# 以后随着学习你会在你的解释器上安装很多第三方的包
# ---> 每次打开pycahrm都有一个自动检索当前安装包的机制
# 如果电脑配置跟不上---> 五分钟
【2】在python中的垃圾
# 每次书写Python代码都会创建很多变量名和变量值
# 但是有很多变量名和变量值 用过一次就不用了
# ---> 被称之为垃圾 ---> 不会主动清理掉垃圾
# name = 'dream'
# print(name)
# del name
# print(name)
# 在python解释器内部自带了一个垃圾回收机制
【3】堆和栈的概念
# 栈区:变量名和值内存地址关联的区域
# 堆区:变量值存放的区域
【4】三个名词
# 引用计数为主,标记清除为辅,分代回收
# 【1】引用计数
# 变量值被变量名指向的次数
# x = 10 # 1
# y = x # 2
# x = 11
# y = 12
# 【2】标记清除
# 当一个变量值被引用的时候,Python自带的垃圾回收机制会定期扫描
# 如果这个变量值有引用 ---> 不管
# 如果这个变量值没有引用 ---> 标记
# 【3】分代回收
# 新生代 --> 第一次被扫描,扔到新生代中
# 青春代 --> 知道达到新生代扫描阈值,如果还没人指向直接挪到 青春代
# 老年代 --> 知道达到青春代扫描阈值,如果还没人指向直接挪到 老年代
# ---> 知道达到老年代扫描阈值,如果还没人指向直接清除 del
【5】栈区和堆区
# 【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】什么是字符编码
- 人类在与计算机交互时,用的都是人类能读懂的字符,如中文字符、英文字符、日文字符等
- 而计算机只能识别二进制数
- 二进制数即由0和1组成的数字,例如010010101010
- 计算机是基于电工作的,电的特性即高低电平
- 人类从逻辑层面将高电平对应为数字1,低电平对应为数字0,这直接决定了计算机可以识别的是由0和1组成的数字
- 所以,由人类的字符到计算机中的数字,一定经历一个过程
- 即
- 翻译的过程必须参照一个特定的标准
- 该标准称之为字符编码表
- 该表上存放的就是字符与数字一一对应的关系。
- 字符编码中的编码指的是翻译或者转换的意思
- 即将人能理解的字符翻译成计算机能识别的数字
# 字符编码
# 为什么要学?
# 不同国家有不同的文字 ---> 让python代码识别不同国家的文字
# 我们交流 ---> 我们就指定好指定的模板让他使用
【2】一家独大(ASCII表)
# 现代计算机起源于美国,所以最先考虑仅仅是让计算机识别英文字符,于是诞生了ASCII表
# 只有英文字符与数字的一一对应关系
# 一个英文字符对应1Bytes,1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
字母对应ASCII表
- A-Z:65-90
- a-z:97-122
- 0-9:48-57
- 小写字母对应的数字一定大于大写字母
【3】多方割据(ASCII/GBK/Shift_JIIS)
- 为了让计算机能够识别中文和英文,中国人定制了
GBK
[1]GBK表的特点
-
只有中文字符、英文字符与数字的一一对应关系
-
一个英文字符对应1Bytes 一个中文字符对应2Bytes
-
补充说明
-
1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
-
2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符
[2]各国不同的编码表
- 为了让计算机能够识别日文字符、英文字符与数字的一一对应关系,日本定制了
Shift_JIS
表 - 为了让计算机能够识别韩文字符、英文字符与数字的一一对应关系,韩国定制了
Euc-kr
表 - 美国人用的计算机里使用字符编码标准是ASCII
- 中国人用的计算机里使用字符编码标准是GBK
- 日本人用的计算机里使用字符编码标准是Shift_JIS
[3]多种编码共存的缺点
- 此时无论是存还是取由于采用的字符编码表一样
- 所以肯定不会出现乱码问题
- 但问题是
- 在美国人用的计算机里只能输入英文字符
- 而在中国人用的计算机里只能输入中文字符和英文字符....
- 毫无疑问我们希望计算机允许我们输入万国字符均可识别、不乱码,而现阶段计算机采用的字符编码ASCII、GBK、Shift_JIS都无法识别万国字符
- 所以我们必须定制一个兼容万国字符的编码表
【4】统一(unicode)
- unicode于1990年开始研发,1994年正式公布
[1]unicode的特点
- 存在所有语言中的所有字符与数字的一一对应关系,即兼容万国字符
- 与传统的字符编码的二进制数都有对应关系,详解如下
- 很多地方或老的系统、应用软件仍会采用各种各样传统的编码,这是历史遗留问题。
- 此处需要强调:软件是存放于硬盘的,而运行软件是要将软件加载到内存的,面对硬盘中存放的各种传统编码的软件,想让我们的计算机能够将它们全都正常运行而不出现乱码,内存中必须有一种兼容万国的编码,并且该编码需要与其他编码有相对应的映射/转换关系,这就是unicode的第二大特点产生的缘由
[2]字符的存储
- 文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的
- 存放于硬盘中,则可以转换成任意其他编码,只要该编码可以支持相应的字符
- 英文字符可以被ASCII识别
- 英文字符--->unciode格式的数字--->ASCII格式的数字
- 中文字符、英文字符可以被GBK识别
- 中文字符、英文字符--->unicode格式的数字--->gbk格式的数字
- 日文字符、英文字符可以被shift-JIS识别
- 日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字
# 如果要使用 ---> 读出来 ---> 展示
# 美国人--ascii ---> unicode ----> gbk ---> 中文
# 日本人--shift-jis ---> unicode ----> gbk ---> 中文
【5】如何进行编解码(二进制)
# 【1】解码 ---> 将二进制数据转换为字符
# data = b'\xe8\x9a\xa9\xe6\xa2\xa6'
# print(data.decode('utf-8'))
# print(data.decode('gbk'))
# print(data.decode('shift-jis'))
#
# name = b'dream'
# print(type(name))
# # 【2】编码 ---> 将字符转换为二进制数据
# name = '蚩梦'
# # 指定编码格式 ---> 转换成那种语言的二进制四数据
# print(name.encode('utf-8'))
【6】UTF-8的诞生
-
如果保存到硬盘的是GBK格式二进制,当初用户输入的字符只能是中文或英文
-
同理如果保存到硬盘的是Shift_JIS格式二进制,当初用户输入的字符只能是日文或英文……
-
如果我们输入的字符中包含多国字符,那么该如何处理?
-
多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(GBK格式的二进制)
-
多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(Shift_JIS格式的二进制)
-
多国字符—√—>内存(unicode格式的二进制)——√—>硬盘(???格式的二进制)
-
utf-8是针对Unicode的可变长度字符编码:
- 一个英文字符占1Bytes,
- 一个中文字符占3Bytes,生僻字用更多的Bytes存储
【四】文件操作
【1】操作文件方式一:open语句
# 1. 打开文件,由应用程序向操作系统发起系统调用open(...),操作系统打开该文件,对应一块硬盘空间,并返回一个文件对象赋值给一个变量f
f = open('a.txt', 'r', encoding='utf-8') # 默认打开模式就为r
# 2. 调用文件对象下的读/写方法,会被操作系统转换为读/写硬盘的操作
data = f.read()
# 3. 向操作系统发起关闭文件的请求,回收系统资源
f.close()
【2】操作文件方式二:with open语句
# 1、在执行完子代码块后,with 会自动执行f.close()
# open(文件路径/文件名, 打开模式,编码方式)
with open('a.txt', 'w', encoding='utf8') as f:
pass
# 2、可用用with同时打开多个文件,用逗号分隔开即可
with open('a.txt', 'r') as read_f, open('b.txt', 'w', encoding='utf8') as write_f:
data = read_f.read()
write_f.write(data)
【3】文件的操作模式
- r(默认的):
- 只读
- w:
- 只写
- a:
- 只追加写
[1]r模式的使用
(1)r只读模式
- 在文件不存在时则报错,文件存在文件内指针直接跳到文件开头
# 通过 with ... open ... 语句打开文件,并将文件内容一次性读出
with open('a.txt', mode='r', encoding='utf-8') as f:
# 会将文件的内容由硬盘全部读入内存,赋值给res
res = f.read()
(2)用户认证功能练习
# 小练习:实现用户认证功能
inp_name = input('请输入你的名字: ').strip()
inp_pwd = input('请输入你的密码: ').strip()
with open(r'db.txt', mode='r', encoding='utf-8') as f:
for line in f:
# 把用户输入的名字与密码与读出内容做比对
u, p = line.strip('\n').split(':')
if inp_name == u and inp_pwd == p:
print('登录成功')
break
else:
print('账号名或者密码错误')
(3)补充
- 当路径中可能存在有转义的字符时
- 或者
\\
取消\
的转义效果 - 字符串前面加上r来解决转义的问题
- 或者
- f:格式化输出
- b:将字符串转为二进制
- r:取消 \ 转义
# 将文件绝对路径做成字符串格式时,由于 \ 在Python中是转义符,因此和不用的字符会产生不同的转义效果
path = 'E:\PythonProjects\01.py' # 错误
# 修正方式一 : \\ 双转义,将原来的 \ 进行反转义
path = 'E:\\PythonProjects\\01.py' # \\ 反转义
# 修正方式二 : 在带 \ 的路径字符串前 + r 进行取消转义
path = r'E:\PythonProjects\01.py' # \\ +r 取消转义
[2]w模式的使用
- w只写模式: 在文件不存在时会创建空文档,文件存在会清空文件,文件指针跑到文件开头
# 一次性写入
data = 'opp|366'
with open(file='01.text', mode='w', encoding='utf8') as fp:
# 写的内容
fp.write(data)
# 连续写入
with open('b.txt',mode='w',encoding='utf-8') as f:
f.write('你好\n')
f.write('我好\n')
f.write('大家好\n')
f.write('111\n222\n333\n')
[3]a模式的应用
- a只追加写模式: 在文件不存在时会创建空文档,文件存在会将文件指针直接移动到文件末尾
with open('c.txt',mode='a',encoding='utf-8') as f:
f.write('44444\n')
f.write('55555\n')
# 小练习:实现注册功能:
name=input('username>>>: ').strip()
pwd=input('password>>>: ').strip()
with open('db1.txt',mode='a',encoding='utf-8') as f:
info='%s:%s\n' %(name,pwd)
f.write(info)
- 强调 w 模式与 a 模式的异同
- 相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前写的内容之后
- 不同点:以 a 模式重新打开文件,不会清空原文件内容,会将文件指针直接移动到文件末尾,新写的内容永远写在最后
[4]+模式的应用
- r+ w+ a+ :可读可写
- 在平时工作中,我们只单纯使用r/w/a,要么只读,要么只写,一般不用可读可写的模式
# 【一】文件操作模式补充
# 【1】文本编辑模式
# w / a / r
# 完整的模式应该是 wt / at rt
# 大文本编辑模式
# 【2】二进制数据模式
# wb / rb
# with open('img.png', 'rb') as fp:
# data = fp.read()
#
# with open('girl.jpg', 'wb') as fp:
# fp.write(data)
【4】文件操作方法
# 【0】数据准备
# data = 'dream|521'
# with open('01.text', 'a', encoding='utf8') as fp:
# fp.write(data + '\n')
# 【1】r模式的方法详细
# with open('01.text', 'r', encoding='utf8') as fp:
# (1)一次性全部读完
# data = fp.read()
# (2)每次只读一行
# count = 0
# while True:
# count += 1
# data = fp.readline()
# if data:
# print(count, data)
# else:
# break
# (3)一次性读很多行 ---> 把所有数据读出来后放到一个列表中
# data = fp.readlines()
# print(data)
# (4)测试当前对象是否可读
# print(fp.readable())
# fp = open('01.text', 'r', encoding='utf8')
# fp.close()
# print(fp.readable())
# 【2】写模式的操作方法
# data_list = ['111', '\n', '222','\n']
# with open('01.text', 'a', encoding='utf-8') as fp:
# # (1)将原本内容清空并一次性写入新内容
# # fp.write('666')
# # (2)逐个元素进行写入
# fp.writelines(data_list)
# with open('01.text', 'w+', encoding='utf-8') as fp:
# fp.write('999')
# # 将文件写完后立马刷新到硬盘中
# fp.flush()
# data = fp.read()
# print(data)
【5】控制文件指针
# read 方法补充
# with open('01.text', 'r', encoding='utf8') as fp:
# # read 可以放参数,参数是读取到哪个索引位置
# data = fp.read(4)
# print(data)
# seek函数
# f.seek(指针移动的字节数,模式控制):
# 0: 默认的模式,该模式代表指针移动的字节数是以文件开头为参照的
# 1: 该模式代表指针移动的字节数是以当前所在的位置为参照的
# 2: 该模式代表指针移动的字节数是以文件末尾的位置为参照的
# with open('girl.jpg', 'rb') as fp:
# 以开头作为参照向后移动1个字符
# 英文字符是占了一个字符的位置 中文字符是占三个字符的位置
# fp.seek(3, 0)
# 查看当前指针所在的索引位置
# print(fp.tell()) # 3
# 0 : 以开头作为参照用一次重置一次
# fp.seek(3, 0)
# print(fp.tell()) # 3
# (2)1 模式: 以当前位置作为参照向后移动
# 在Python3.x版本之后不允许在文本文件中使用
# 只能在二进制中使用
# print(fp.read()) # 真是帅爆啦!
# fp.seek(3, 1)
# (2)2模式:以结尾位置作为参照移动
# fp.seek(-2, 2) # \x82'
# print(fp.tell())
# print(fp.read())
练习
'''
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
'''
"""
张一蛋<妇女主任> 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
"""
# 打开文件
# 打开的模式有一些变化
# w 会覆盖写
# a 会追加在结尾
# with open('01.text', 'w+', encoding='utf-8') as fp:
# fp.seek(9, 0)
# fp.write('<妇女主任>')
# r :读 rt
# r+
# with open('01.text', 'r+', encoding='utf-8') as fp:
# fp.seek(9, 0)
# fp.write('<妇女主任>')
# # 回到开头读
# fp.seek(0, 0)
# data = fp.read()
# print(data)
'''
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
'''
'''
张一蛋(妇女主任) 山东 179 49 12344234523
李二蛋(村长) 河北 163 57 13913453521
王全蛋(书记) 山西 153 62 18651433422
'''
# line_list = []
# for line in data:
# 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)
#
# with open('01.txt', 'w', encoding='utf-8') as fp:
# fp.writelines(line_list)
标签:fp,编码,字符,list,---,print,拷贝,data
From: https://www.cnblogs.com/song1002/p/18136958