随笔- 71  文章- 17  评论- 0  阅读- 5326 

引入

应用程序运行过程中产生的数据最先都是存放于内存中的,若想永久保存下来,必须要保存于硬盘中。应用程序若想操作硬件必须通过操作系统,而文件就是操作系统提供给应用程序来操作硬盘的虚拟概念,用户或应用程序对文件的操作,就是向操作系统发起调用,然后由操作系统完成对硬盘的具体操作。

基本流程

  • 有了文件的概念,我们无需再去考虑操作硬盘的细节,只需要关注操作文件的流程:
    1.打开文件(open()):由应用程序向操作系统发起系统调用open(...),操作系统打开该文件,对应一块硬盘空间,并返回一个文件对象赋值给一个变量f
    2.操作文件(f.read()/f.write()):调用文件对象下的读/写方法,会被操作系统转换为读/写硬盘的操作
    3.关闭文件(f.close()):向操作系统发起关闭文件的请求,回收系统资源

open函数

  • 概念:用Python内置的open()函数打开一个文件,创建一个file对象,相关的方法才可以调用它进行读写
  • 语法:
open(文件路径, 读写模式, 字符编码)

read方法

  • read()方法:从一个打开的文件中读取一个字符串
  • 注意:Python字符串可以是二进制数据,而不是仅仅是文字
  • 语法:
f.read()

write方法

  • write()方法:可将任何字符串写入一个打开的文件
  • 注意:
    • Python字符串可以是二进制数据,而不是仅仅是文字
    • write()方法不会在字符串的结尾添加换行符('\n'):
  • 语法:
f.write()

close方法

  • close()方法:刷新缓冲区里任何还没写入的信息,并关闭该文件,这之后便不能再进行写入
  • 注意:当一个文件对象的引用被重新指定给另一个文件时,Python会关闭之前的文件,用 close()方法关闭文件是一个很好的习惯
  • 语法:
f.write()

资源回收与with上下文管理

资源回收

  • 打开一个文件包含两部分资源:应用程序的变量f和操作系统打开的文件。在操作完毕一个文件时,必须把与该文件的这两部分资源全部回收,回收方法为:
    1. f.close():回收操作系统打开的文件资源
    2. del f:回收应用程序级的变量
      • del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件无法关闭,白白占用资源,Python自动的垃圾回收机制决定了我们无需考虑del f
      • 在操作完毕文件后,一定要记住f.close(),但是大多数人还是会不由自主地忘记f.close(),考虑到这一点,python提供了with关键字来帮我们管理上下文

with方法

  • 在执行完子代码块后,with 会自动执行f.close()
with open('a.txt','w') as f:
pass
# pass 补全语法结构,本身没有任何功能(推荐使用)
# ... 补全语法结果,本身没有任何功能(不推荐使用)
  • 可用用with同时打开多个文件,用逗号分隔开即可
with open('a.txt','r') as read_f,open('b.txt','w') as write_f:
data = read_f.read()
write_f.write(data)

文件操作模式

控制文件读写操作的模式

r模式

  • r(read)模式:只读模式,只能读不能写
# 文件路径不存在: r模式直接报错
with open(r'c.txt', 'r', encoding='utf8') as f:
pass
# 文件路径存在: r模式会打开文件等待读取文件内容
with open(r'a.txt', 'r', encoding='utf8') as f:
# print(f.read())
f.write('123')

w模式

  • w(write)模式:只写模式,只能写不能读
  • 在文件不关闭的情况下,连续的写入,后写的内容一定跟在前写内容的后面
  • 如果重新以w模式打开文件,则会清空文件内容
# 文件路径不存在: w模式会自动创建该文件
with open(r'c.txt', 'w', encoding='utf8') as f:
pass
# 文件路径存在: w模式会先清空该文件内容 之后等待填写新内容
with open(r'a.txt', 'w', encoding='utf8') as f:
pass

a模式

  • a(append)模式:只追加模式,在文件末尾添加内容
# 文件路径不存在: a模式会自动创建该文件
with open(r'd.txt', 'a', encoding='utf8') as f:
pass
# 文件路径存在: a模式不会先清空该文件内容 只在在文件末尾等待填写新内容
with open(r'b.txt', 'a', encoding='utf8') as f:
f.write('阿巴阿巴\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)

+模式

  • r+/w+/a+:可读可写
  • 在平时工作中,我们只单纯使用r/w/a,要么只读,要么只写,一般不用可读可写的模式

w模式与a模式异同

  • 相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前写的内容之后
  • 不同点:以a模式重新打开文件,不会清空原文件内容,会将文件指针直接移动到文件末尾,新写的内容永远写在最后

控制文件读写内容的模式

t模式

  • t模式(rt/wt/at 可以省略t):文本模式(文件操作的默认模式)
    1.只能操作文本文件
    2.必须要指定encoding参数
    3.读写都是以字符串为单位
# t 模式:如果我们指定的文件打开模式为r/w/a,其实默认就是rt/wt/at
with open('a.txt',mode='rt',encoding='utf-8') as f:
res=f.read()
with open('a.txt',mode='wt',encoding='utf-8') as f:
s='abc'
f.write(s) # 写入的也必须是字符串类型

b模式

  • b模式(rb/wb/ab 不能省略b):二进制模式(可以操作任意类型的文件)
    1.能够操作所有类型的文件
    2.不需要指定encoding参数
    3.读写都是以bytes为单位
# b: 读写都是以二进制位单位
with open('1.mp4',mode='rb') as f:
data=f.read()
print(type(data)) # 输出结果为:<class 'bytes'>
with open('a.txt',mode='wb') as f:
msg="你好"
res=msg.encode('utf-8') # res为bytes类型
f.write(res) # 在b模式下写入文件的只能是bytes类型
# 小练习: 编写拷贝工具
src_file=input('源文件路径: ').strip()
dst_file=input('目标文件路径: ').strip()
with open(r'%s' %src_file,mode='rb') as read_f,open(r'%s' %dst_file,mode='wb') as write_f:
for line in read_f:
# print(line)
write_f.write(line)

b模式对比t模式

  • 纯文本文件:t模式省去编码和解码的环节,b模式需要手动编码和解码,t模式更方便
  • 非文本文件(图片、视频、音频):只能使用b模式
  • t/b模式都不可以单独使用,必须与r/w/a之一结合使用

控制文件内指针移动

  • 文本模式:read()括号内的数字表示读取几个字符
with open(r'a.txt', 'r', encoding='utf8') as f:
data = f.read(3)
print(data)
  • 二进制模式:read()括号内的数字表示读取及格字节(英文1字节,中文3字节)
with open(r'a.txt', 'rb') as f:
data = f.read(3)
print(data.decode('utf8'))

tell方法

  • .tell():获取光标移动的字节数

seek方法

  • 之前文件内指针的移动都是由读/写操作而被动触发的,若想读取文件某一特定位置的数据,则需要用seek方法主动控制文件内指针的移动
  • .seek(offset,whence)
    • offset:指针移动的字节数
    • whence:模式控制
      • 0模式(默认模式):基于文件开头移动的多少字节
      • 1模式:基于光标当前所在位置移动多少字节(只能在二进制模式(b模式)下使用)
      • 2模式:基于文件末尾移动多少字节(只能在二进制模式(b模式)下使用)

0模式详解

# a.txt用utf-8编码,内容如下(abc各占1个字节,中文“你好”各占3个字节)
abc你好
# 0模式的使用
with open('a.txt',mode='rt',encoding='utf-8') as f:
f.seek(3,0) # 参照文件开头移动了3个字节
print(f.tell()) # 查看当前文件指针距离文件开头的位置,输出结果为3
print(f.read()) # 从第3个字节的位置读到文件末尾,输出结果为:你好
# 注意:由于在t模式下,会将读取的内容自动解码,所以必须保证读取的内容是一个完整中文数据,否则解码失败
with open('a.txt',mode='rb') as f:
f.seek(6,0)
print(f.read().decode('utf-8')) #输出结果为: 好

1模式详解

# 1模式的使用
with open('a.txt',mode='rb') as f:
f.seek(3,1) # 从当前位置往后移动3个字节,而此时的当前位置就是文件开头
print(f.tell()) # 输出结果为:3
f.seek(4,1) # 从当前位置往后移动4个字节,而此时的当前位置为3
print(f.tell()) # 输出结果为:7

2模式详解

# a.txt用utf-8编码,内容如下(abc各占1个字节,中文“你好”各占3个字节)
abc你好
# 2模式的使用
with open('a.txt',mode='rb') as f:
f.seek(0,2) # 参照文件末尾移动0个字节, 即直接跳到文件末尾
print(f.tell()) # 输出结果为:9
f.seek(-3,2) # 参照文件末尾往前移动了3个字节
print(f.read().decode('utf-8')) # 输出结果为:好
# 小练习:实现动态查看最新一条日志的效果
import time
with open('access.log',mode='rb') as f:
f.seek(0,2)
while True:
line=f.readline()
if len(line) == 0:
# 没有内容
time.sleep(0.5)
else:
print(line.decode('utf-8'),end='')

文件的其他操作方法

  • read()方法:读取所有内容
    -执行完读取内容之后,光标在文件末尾,继续读取没有内容
    -当文件内容特别大的时候,容易造成内存溢出(满了),不推荐一次性读取

  • readline()方法:一次只读取一行内容,光标移动到第二行首部

  • readlines()方法:读取每一行内容,存放于列表中

  • readable()方法:判断当前文件是否可读

  • write()方法:填写文件内容(字符串或者bytes类型)

  • writeable()方法:判断当前是否可写

  • writelines()方法:支持填写内容容器类型(内部可以存放多个数据值的数据类型)多个数据值

  • flush方法:将内存中的文件数据立刻刷到硬盘(相当于主动按Ctrl+s)

文件数据修改

覆盖写

  • 实现思路:先读取文件的内容一次性全部读入到内存,在内存中完成修改再覆盖写回原文件
  • 优点:硬盘只占用一块空间
  • 缺点:数据量较大的时候会造成内存溢出
with open(r'demo.txt','r',encoding='utf8') as f:
data = f.read()
new_data = data.replace('DSB','NB')
with open(r'demo.txt','w',encoding='utf8') as f1:
f1.write(new_data)

重命名和删除文件

重命名rename方法

  • rename()方法:需要两个参数,当前文件名和新文件名
  • 实现思路:先读取文件内容到内存,在内存中完成修改,之后保存到另一个文件中再将原文件删除,将新的文件重命名为原文件
  • 优点:不会占用过多的内存(不会造成内存溢出)
  • 缺点:在文件修改过程中同一份数据存了两份,也可能是在内存中创建没有刷到硬盘
import os
with open('demo.txt', 'r', encoding='utf8') as read_f, \
open('.demo.txt.swap', 'w', encoding='utf8') as wrife_f:
for line in read_f:
wrife_f.write(line.replace('NB', 'SB'))
os.rename('.demo.txt.swap', 'demo.txt') # 重命名文件

删除文件remove方法

  • remove()方法:需要提供要删除的文件名作为参数
import os
with open('demo.txt', 'r', encoding='utf8') as read_f, \
open('.demo.txt.swap', 'w', encoding='utf8') as wrife_f:
for line in read_f:
wrife_f.write(line.replace('NB', 'SB'))
os.remove('demo.txt') # 删除文件

重点例题

# 利用文件充当数据库编写用户登录、注册功能
# 文件名称:userinfo.txt
# 基础要求:
# 用户注册功能>>>:文件内添加用户数据(用户名、密码等)
# 用户登录功能>>>:读取文件内用户数据做校验
# ps:上述功能只需要实现一次就算过关(单用户) 文件内始终就一个用户信息
# 拔高要求:
# 用户可以连续注册
# 用户可以多账号切换登录(多用户) 文件内有多个用户信息
# ps:思考多用户数据情况下如何组织文件内数据结构较为简单
# 提示:本质其实就是昨天作业的第二道题 只不过数据库由数据类型变成文件
while True:
print("""
1.用户注册
2.用户登录
3.退出
""")
choice = input('请输入您想要操作的指令:')
if choice == '1':
user_name = input('请输入用户名:').strip()
with open(r'userinfo.txt', 'r', encoding='utf8') as f:
for line in f:
real_name = line.split('|')[0]
if real_name == user_name:
print('用户名已存在,请重新输入')
break
else:
user_pwd = input('请输入密码:').strip()
user_data = '%s|%s\n' % (user_name, user_pwd)
with open(r'userinfo.txt', 'a', encoding='utf8') as f:
f.write(user_data)
print(f'用户{user_name}注册成功')
elif choice == '2':
user_name = input('请输入用户名:').strip()
user_pwd = input('请输入密码:').strip()
with open(r'userinfo.txt', 'r', encoding='utf8') as f:
for line in f:
real_name, real_pwd = line.split('|')
if user_name == real_name and user_pwd == real_pwd.rstrip('\n'):
print('用户登录成功')
break
else:
print('用户名或密码错误,用户登录失败')
elif choice == '3':
print('退出成功')
break
else:
print('输入指令错误,请重新输入')
 posted on   念白SAMA  阅读(292)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示