Python中的文件操作

1. 永久存储

当我们在说 “永久存储” 的时候,是希望将数据保存到硬盘上,而非内存,因为内存在计算机断电后数据将会丢失。

2. 打开文件

使用 Python 打开一个文件,我们需要用到 open() 函数:

>>> f = open("FishC.txt", "w")

第一个参数指定的是文件路径和文件名,这里我们没有添加路径的话,那么默认是将文件创建在 Python 的主文件夹下面,因为执行 IDLE 的程序就放在那里嘛(同样的道理,如果我们在桌面创建一个 test.py 的源文件,然后输入打开文件的代码,那么它就会在桌面创建一个 FishC.txt 的文本文件)。
第二个参数是指定文件的打开模式:

3. 文件对象方法详细解析

(1)有两个方法可以将字符串写入到文本对象种,一个是 write(),一个是 writelines():


>>> f.write("I love Python.")
14

使用 write() 方法,它有一个返回值,就是总共写入到文件对象中的字符个数。

使用 writelines() 方法,则可以将多个字符串同时写入:

>>> f.writelines(["I love FishC.\n", "I love my wife."])
>>>

注意:虽然 writelines() 方法支持传入多个字符串,但它不会帮你添加换行符,所以我们要自己添加才行。[/code]

4. 关闭文件

我们使用 close() 方法来关闭文件:

>>> f.close()
>>>

注意,文件对象关闭之后,我们就没办法对它进行操作了。

如果想要继续操作文件,那么我们必须重新打开它。

5.路径修改

不同操作系统,它的这个路径分隔符是不一样的,比如 Windows 系统是使用反斜杠,而其他大多数操作系统则是使用斜杠来分隔。

这个 Windows 采用的反斜杠,跟字符串中的转义字符的反斜杠是同一条杠,那么,如果你想要在 Windows 上使用反斜杠来分隔路径,你就不得不用另一条反斜杠来转移反斜杠,或者使用原始字符串,并不是说这么做有多困难吧,就是觉得这样做不是那么优雅……

6.相对路径和绝对路径

相对路径:以当前目录作为基准,进而逐级推导(. . 表示上级目录;. 当前目录)。
绝对路径:文件真正存在的路径,从根目录逐级推导到指定的文件 / 文件夹。

7. pathlib.Path 实用功能讲解

使用 Path 里面的 cwd() 方法来获取当前的工作目录:

>>> Path.cwd()
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39')

创建路径对象:

>>> p = Path('C:/Users/goodb/AppData/Local/Programs/Python/Python39')

使用斜杠(/)直接进行路径拼接:

>>> q = p / "FishC.txt"
>>> q
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39/FishC.txt')

使用 is_dir() 方法可以判断一个路径是否为一个文件夹:

>>> p.is_dir()
True
>>> q.is_dir()
False

使用 is_file() 方法可以判断一个路径是否为一个文件:

>>> p.is_file()
False
>>> q.is_file()
True

通过 exists() 方法测试指定的路径是否真实存在:

>>> p.exists()
True
>>> q.exists()
True
>>> Path("C:/404").exists()
False

使用 name 属性去获取路径的最后一个部分:

>>> p.name
'Python39'
>>> q.name
'FishC.txt'

stem 属性用于获取文件名,suffix 属性用于获取文件后缀:

>>> q.stem
'FishC'
>>> q.suffix
'.txt'

parent 属性用于获取其父级目录:

>>> p.parent
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python')
>>> q.parent
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39')

加个复数,parents,还可以获得其逻辑祖先路径构成的一个不可变序列:

>>> p.parents
<WindowsPath.parents>
>>> ps = p.parents
>>> for each in ps:
...     print(each)
...         
C:\Users\goodb\AppData\Local\Programs\Python
C:\Users\goodb\AppData\Local\Programs
C:\Users\goodb\AppData\Local
C:\Users\goodb\AppData
C:\Users\goodb
C:\Users
C:\

还支持索引:

>>> ps[0]
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python')
>>> ps[1]
WindowsPath('C:/Users/goodb/AppData/Local/Programs')
>>> ps[2]
WindowsPath('C:/Users/goodb/AppData/Local')

parts 属性将路径的各个组件拆分成元组的形式:

>>> p.parts
('C:\\', 'Users', 'goodb', 'AppData', 'Local', 'Programs', 'Python', 'Python39')
>>> q.parts
('C:\\', 'Users', 'goodb', 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'FishC.txt')

最后,还可以查询文件或文件夹的状态信息:

>>> p.stat()
os.stat_result(st_mode=16895, st_ino=281474976983758, st_dev=1289007019, st_nlink=1, st_uid=0, st_gid=0, st_size=4096, st_atime=1648462096, st_mtime=1648205377, st_ctime=1605695407)
>>> q.stat()
os.stat_result(st_mode=33206, st_ino=4503599627467517, st_dev=1289007019, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1648206152, st_mtime=1648206152, st_ctime=1648205377)

比如这个 st_size 就是文件或文件夹的尺寸信息:

>>> p.stat().st_size
4096
>>> q.stat().st_size
0

使用 resolve() 方法可以将相对路径转换为绝对路径:

>>> Path('./doc').resolve()
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39/Doc')
>>> Path('../FishC').resolve()
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/FishC')

最后还可以通过 iterdir() 获取当前路径下面的所有子文件和子文件夹对象:

>>> p.iterdir()
<generator object Path.iterdir at 0x0000012D57CBE660>

它生成的是一个迭代器对象,所以可以放到 for 语句中去提取数据:

>>> for each in p.iterdir():
...     print(each.name)
... 
DLLs
Doc
FishC
FishC.txt
include
Lib
libs
LICENSE.txt
NEWS.txt
python.exe
python3.dll
python39.dll
pythonw.exe
Scripts
tcl
Tools
vcruntime140.dll
vcruntime140_1.dll

如果我们要将当前路径下面的所有文件整理成一个列表,可以这么做(注意,是文件,不包含文件夹,所以我们要加一个条件过滤):

>>> [x for x in p.iterdir() if x.is_file()]
[WindowsPath('FishC.txt'), WindowsPath('LICENSE.txt'), WindowsPath('NEWS.txt'), WindowsPath('python.exe'), WindowsPath('python3.dll'), WindowsPath('python39.dll'), WindowsPath('pythonw.exe'), WindowsPath('vcruntime140.dll'), WindowsPath('vcruntime140_1.dll')]

以上是用得比较多的,与路径查询相关的操作。

那么修改路径也是支持的,比如我们可以使用 mkdir() 方法来创建文件夹:

>>> n = p / "FishC"
>>> n.mkdir()
>>>

注意,如果需要创建的文件夹已经存在,那么它就会报错:

>>> n.mkdir()
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    n.mkdir()
  File "C:\Users\goodb\AppData\Local\Programs\Python\Python39\lib\pathlib.py", line 1323, in mkdir
    self._accessor.mkdir(self, mode)
FileExistsError: [WinError 183] 当文件已存在时,无法创建该文件。: 'C:\\Users\\goodb\\AppData\\Local\\Programs\\Python\\Python39\\FishC'

也可以避开这个报错信息,我们设置其 exist_ok 参数的值为 True 即可:

>>> n.mkdir(exist_ok=True)
>>>

还有一点需要注意的就是,如果路径中有存在多个不存在的父级目录,那么也会出错的,比如这样:

>>> n = p / "FishC/A/B/C"
>>> n.mkdir(exist_ok=True)
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    n.mkdir(exist_ok=True)
  File "C:\Users\goodb\AppData\Local\Programs\Python\Python39\lib\pathlib.py", line 1323, in mkdir
    self._accessor.mkdir(self, mode)
FileNotFoundError: [WinError 3] 系统找不到指定的路径。: 'C:\\Users\\goodb\\AppData\\Local\\Programs\\Python\\Python39\\FishC\\A\\B\\C'

它也定义了一个参数用于对付这种情况,将 parents 参数设置为 True 就可以了:

>>> n.mkdir(parents=True, exist_ok=True)
>>>

Path 内部其实还打包了一个 open() 方法,除了不用传入路径之外,其它参数跟 open() 函数是一摸一样的:

>>> n = n / 'FishC.txt'
>>> n
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39/FishC/A/B/C/FishC.txt')
>>> f = n.open('w')
>>> f.write("I love FishC.")
13
>>> f.close()

可以给文件或文件夹修改名字,使用 rename() 方法来实现:

>>> n.rename("NewFishC.txt")
WindowsPath('NewFishC.txt')

然后使用 replace() 方法替换文件或文件夹:

>>> m = Path("NewFishC.txt")
>>> n
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39/FishC/A/B/C/FishC.txt')
>>> m.replace(n)
WindowsPath('C:/Users/goodb/AppData/Local/Programs/Python/Python39/FishC/A/B/C/FishC.txt')

还有删除操作,rmdir() 和 unlink() 方法,前者用于删除文件夹,后者用于删除文件:

>>> n.parent.rmdir()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
n.parent.rmdir()
File "C:\Users\goodb\AppData\Local\Programs\Python\Python39\lib\pathlib.py", line 1363, in rmdir
self._accessor.rmdir(self)
OSError: [WinError 145] 目录不是空的。: 'C:\\Users\\goodb\\AppData\\Local\\Programs\\Python\\Python39\\FishC\\A\\B\\C'

可以看到,如果不是空文件夹,它是删不掉的,我们需要先把里面的文件删了:

>>> n.unlink()
>>>

现在再删除文件夹,就 OK 啦:

>>> n.parent.rmdir()
>>>

最后是功能强大的查找,由 glob() 方法来实现:

>>> p = Path('.')
>>> list(p.glob("*.txt"))
[WindowsPath('FishC.txt'), WindowsPath('LICENSE.txt'), WindowsPath('NEWS.txt')]

这就查找当前目录下的所有 .txt 后缀的文件,如果要查找当前目录的下一级目录中的所有 .py 后缀的文件,可以这么写:

>>> p = Path('.')
>>> list(p.glob("*.txt"))
[WindowsPath('FishC.txt'), WindowsPath('LICENSE.txt'), WindowsPath('NEWS.txt')]

这就查找当前目录下的所有 .txt 后缀的文件,如果要查找当前目录的下一级目录中的所有 .py 后缀的文件,可以这么写:

>>> list(p.glob('*/*.py'))
[WindowsPath('Lib/abc.py'), WindowsPath('Lib/aifc.py'), WindowsPath('Lib/antigravity.py'), ...]

好了,那么如果希望进行向下递归搜索,也就是查找当前目录以及该目录下面的所有子目录,可以使用两个星号(**)表示:

>>> list(p.glob('**/*.py'))
[WindowsPath('Lib/abc.py'), WindowsPath('Lib/aifc.py'), WindowsPath('Lib/antigravity.py'), ...]

8.with语句和上下文管理器

上下文管理器为文件操作提供了一种更为优雅的实现方式。

我们先来看一下传统的文件操作实现:

>>> f = open("FishC.txt", "w")
>>> f.write("I love FishC.")
13
>>> f.close()

总结下来无非就是三板斧:打开文件 -> 操作文件 -> 关闭文件

那么使用 with 上下文管理器方案,应该如何实现呢?

>>> with open("FishC.txt", "w") as f:
...     f.write("I love FishC.")
... 
13

两者是等效的,通俗来讲,对于文件操作这样的三板斧来说,上文就是打开文件,下文就是关闭文件,这个就是上下文管理器做的事情。

使用上下文管理器,最大的优势是能够确保资源的释放(在这里就是文件的正常关闭)。

9.pickle

pickle 模块支持你将 Python 的代码序列化,解决的就是一个永久存储 Python 对象的问题。

说白了,就是将咱们的源代码,转变成 0101001 的二进制组合。

掌握 pickle,只需要学习两个函数的用法:一个是 dump(),另一个是 load()。

使用 dump() 函数将数据写入文件中(文件后缀要求是 .pkl):

import pickle
    
x, y, z = 1, 2, 3
s = "FishC"
l = ["小甲鱼", 520, 3.14]
d = {"one":1, "two":2}
    
with open("data.pkl", "wb") as f:
    pickle.dump(x, f)
    pickle.dump(y, f)
    pickle.dump(z, f)
    pickle.dump(s, f)
    pickle.dump(l, f)
    pickle.dump(d, f)

使用 load() 函数读取 pickle 文件中的数据:

import pickle
    
with open("data.pkl", "rb") as f:
    x = pickle.load(f)
    y = pickle.load(f)
    z = pickle.load(f)
    s = pickle.load(f)
    l = pickle.load(f)
    d = pickle.load(f)
    
print(x, y, z, s, l, d, sep="\n")

如果觉得反复写很多个 dump() 和 load() 太麻烦了,可以将多个对象打包成元组后再进行序列化:

...
pickle.dump((x, y, z, s, l, d), f)
...
x, y, z, s, l, d = pickle.load(f)
...

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年12月4日
下一篇 2023年12月4日

相关推荐