Wlgls 冲鸭!

Python文件操作

2019-07-23

感觉写博客的时候,为了创建文件很麻烦,在创建文件的时候,需要给文件名特定的格式。而在写内容的时候也要加上特定的文件头。这实在让人头秃。

所以学习一下Python对文件的操作,然后在之后创建一个脚本。

参考文档

掘金–代码与艺术的博客

官方文档–pathlib

官方文档–os.path

官方文档–os

菜鸟教程–python os

我们将通过学习以下几个部分来掌握这件事情。

  • 获取文件属性
  • 创建与删除与重命名目录和文件
  • 文件名模式匹配
  • 对文件的读写操作

我们有一个test的文件,他下面的那个结构是

test
├── file1.py
├── file2.py
├── test1
│   └── file3.py
└── test2
    └── file4.py

使用pathlib模块来处理

2019-10-17-13-53-21.png

pathlib模块是提供来表示文件系统路径的类,在不同的操作系统中,我们有不同的子模块可以使用,但是如果以前从未使用过该模块,则Path是最好的选择,他与操作系统无关。

本博客只记录了一些我认为我需要的内容,其余详细,查阅官方文档

初步使用

导入:

>>> from pathlib import Path

实例化一个对象:

>>> p = Path('test')
>>> p
PosixPath('test')
>>> type(p)
<class 'pathlib.PosixPath'>
>>> p.name
'test'

几个重要的属性

  • Path.parts

    一个元组,路径的多个组件

      >>> p = Path('test')
      >>> p.parts
      ('test',)
      >>> p = Path('home/smith/Desktop/test')
      >>> p.parts
      ('home', 'smith', 'Desktop', 'test')
    
  • Path.parents

    提供对路径逻辑祖先的访问的不可变序列,我们将得到包含当前路径的所有祖先

      >>> p = Path('home/smith/Desktop/test')
      >>> p_parents
      <PosixPath.parents>
      >>> p_parents = p.parents
      >>> p_parents[0]
      PosixPath('home/smith/Desktop')
      >>> p_parents[1]
      PosixPath('home/smith')
      >>> p_parents[2]
      PosixPath('home')
      >>> p_parents[3]
      PosixPath('.')
    
  • Path.parent

    此路径的逻辑父路径

      >>> p.parent
      PosixPath('home/smith/Desktop')
    
  • Path.name

    一个表示最后路径组建的字符串,排除了驱动器和根目录

      >>> p.name
      'test'
      >>> p1 = Path('/')
      >>> p1.name
      ''
    
  • Path.suffix

    最后一个组件的文件扩展名,如果没有,返回空字符串

      >>> p = Path('test.py')
      >>> p.suffix
      '.py'
    
  • Path.stem

    最后一个组件的文件名,去掉扩展名

最后一个路径组件,去除后缀

获取当前目录和当前用户家的目录

本过程我们需要使用Path.cwd()方法

  • Path.cwd()

    返回一个新的表示当前目录的路径对象

  • Path.home()

    返回一个当前用户家目录的新路径对象

我们直接使用Path.cwd()就可以获得当前路径的路径对象,根据不同的操作系统,会返回不同的对象

>>> Path.cwd()
PosixPath('/home/smith/Desktop')
>>> type(Path.cwd())
<class 'pathlib.PosixPath'>

Path.home()Path.cwd()使用是一致的

>>> Path.home()
PosixPath('/home/smith')

获得文件属性

  • Path.iterdir()

    当我们实例化的对象是一个目录时,返回一个包含路径下所有对象的生成器

  • Path.is_dir():

    如果路径指向的是一个目录,则返回True,否则,返回False

  • Path.is_file():

    如果路径指向的是一个正常的文件,则返回True, 否则返回False

  • Path.exists():

    如果路径指向的是一个已存在的文件或目录,则返回True,否则返回False

  • Path.stat():

    返回此路径的信息。结果在每次调用此方法时都重新产生。

获取目录下列表

我们使用pathlib.Path()来返回一个poxispath对象,然后我们就可以调用.iterdir()来返回一个生成器,他包含了给定的路径下的所有的目录和文件。

>>> p
PosixPath('test')
>>> p.iterdir()
<generator object Path.iterdir at 0x7f1ffa47bf48>
>>> type(p.iterdir())
<class 'generator'>
>>> for child in p.iterdir(): child
... 
PosixPath('test/file2.py')
PosixPath('test/test2')
PosixPath('test/test1')
PosixPath('test/file1.py')

判断目录或文件是否存在

无论我们给出的路径是否存在,我们都可以使用Path实例化对象,所以我们可以使用exists()来判断是否存在

>>> p.exists()
True
>>> p1 = Path('hello')
>>> p1.exists()
False

获得子目录或子文件

我们可以使用is_dir()is_file()来判断Path对象是否是文件或目录

>>> p
PosixPath('test')
>>> p.is_dir()
True
>>> p.is_file()
False

如果我们想要获得所有的子目录,我们就可以在遍历的时候使用is_dir()方法

>>> for child in p.iterdir():
...     if child.is_dir() :
...             print(child)
... 
test/test2
test/test1

也许你可以尝试一下生成器表达式,他会更加便捷

>>> files_child = (child for child in p.iterdir() if child.is_file())
>>> for item in files_child:
...     print(item)
... 
test/file2.py
test/file1.py

获取文件属性

我们可以直接使用.stat()方法来获取属性,Path.stat()os.stat()十分类似 其中,.stat()将返回一个os.stat_result对象 他的几个重要属性:

  • st_mode: inode保护模式
  • st_ino: unix上inode保护号
  • st_dev: 此文件驻留的设备的标识号
  • st_nlink:链接数
  • st_uid: 所有者的用户ID
  • st_gid: 所有者的组ID
  • st_size: 文件的大小(以字节为单位)
  • st_atim e: 上次访问的时间,以秒为单位
  • st_mtime: 最近内容修改的时间, 以秒为单位
  • st_ctime: 在unix上是最新的元数据更改的时间,在windows是创建时间
>>> p.stat()
os.stat_result(st_mode=16893, st_ino=8126768, st_dev=66315, st_nlink=4, st_uid=1000, st_gid=1000, st_size=4096, st_atime=1564318502, st_mtime=1564318500, st_ctime=1564318500)
>>> p.stat().st_mtime
1564318500.2075815

创建,删除, 重命名,移动目录或文件

  • Path.mkdir(mode=0o777, parents=False, exist_ok=False)

    新建给定路径的目录。

    mode: 访问权限,默认是777。

    parents: Ture或者False, 默认为False。 如果为True,则,给定的路径中任何找不到的父目录都会随着该路径被创建。如果是False,则如果找不到父级目录会抛出FileNotFoundError错误

    exist_ok: True或者False, 默认为False。 如果为False, 则在目标存在的的情况下,抛出FileExistsError,如果为True, 则,如果所给路径最后一个组件不是现存的非目录文件时,则忽略FileExistsError

  • Path.touch(mode=0o666, exist_ok=True)

    创建给定路径的文件

    mode: 访问权限,默认为666, 每个人都有读和写的权限

    exist_ok: True或者False, 默认为True。如果文件已经存在,则当exist_ok为true则函数仍会成功,否则抛出FileExistsError

  • Path.rmdir()

    移除目录,目录必须是空的

  • Path.unlink()

    移除文件。如果路径指向目录,会抛出IsADirectoryError错误

  • Path.rename(target)

    使用给定的target将文件重命名

  • Path.chmod(mode)

    改变文件的模式和权限

  • Path.replace(target)

    将文件名目录重命名为给定的 target, 如果 target 指向一个现有文件或目录,则它将被无条件地替换。

创建目录

在创建目录时, 我们使用Path.mkdir()方法,这个方法包含三个方法,其中mode是我们对于所传建文件设置的读写权限,默认是777, 也就是每个人都有读和写以及执行的权限。

parents参数可以控制我们是否创建父级目录,如果是True,我们就可以创建多个目录,如果是False,则会自动查找父级目录,如果没有父级目录,就会抛出错误。

>>> p1 = Path('test/2019/07/29')
>>> p1.mkdir(parents=True)
>>> exit()
smith@smithgua:~/Desktop$ cd test
smith@smithgua:~/Desktop/test$ tree
.
├── 2019
│   └── 07
│       └── 29
├── file1.py
├── file2.py
├── test1
│   └── file3.py
└── test2
    └── file4.py

smith@smithgua:~/Desktop/test$ cd ..
smith@smithgua:~/Desktop$ python
>>> from pathlib import Path
>>> p2 = Path('test/2018/07/29')
>>> p2.mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1251, in mkdir
    self._accessor.mkdir(self, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'test/2018/07/29'

exist_ok参数则可以用来解决我们我们所创建的文件已经存在的问题,如果为False,则如果我们给出的路径是已经存在的,那么就会抛出错误,如果是True的话,则会忽略这个错误,但同样不再创建目录。但如果我们给出的路径最后一个组件是一个非目录文件,那么无论exist_ok是什么,都会抛出错误

>>> p1 = Path('test/2019')
>>> p1.mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1251, in mkdir
    self._accessor.mkdir(self, mode)
FileExistsError: [Errno 17] File exists: 'test/2019'
>>> p1.mkdir(exist_ok=True)
>>> p2 = Path('test/file1.py')
>>> p2.mkdir(exist_ok=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1251, in mkdir
    self._accessor.mkdir(self, mode)
FileExistsError: [Errno 17] File exists: 'test/file1.py'

创建文件

pathlib模块中,我们使用Path.touch()方法来创建文件,他包含两个参数,第一个参数是mode,用来设置权限,第二个参数是exist_ok,用来处理文件已经存在的情况,在文件已经存在的情况下,如果exist_ok=True,则函数仍会成功,但在表面上,文件内容没有发生任何改变。如果exist_ok=False,则抛出FileExistsError错误。

>>> p2 = Path('test/file1.py')
>>> p2.touch()
>>> p2.touch(exist_ok=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1241, in touch
    fd = self._raw_open(flags, mode)
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1048, in _raw_open
    return self._accessor.open(self, flags, mode)
FileExistsError: [Errno 17] File exists: 'test/file1.py'

移除目录

我们直接使用Path.rmdir()方法来移除目录,要保证目录是空的,如果目录是空的,否则抛出OSError。如果目录不存在,则抛出FileNotFoundError

>>> p
PosixPath('test/2019/07/29')
>>> p.rmdir()
>>> p
PosixPath('test/2019/07/29')
>>> p.exists()
False
>>> p_parent = p.parents[2]
>>> p_parent
PosixPath('test')
>>> p_parent.rmdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1295, in rmdir
    self._accessor.rmdir(self)
OSError: [Errno 39] Directory not empty: 'test'

>>> p.exists()
False
>>> p.rmdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1295, in rmdir
    self._accessor.rmdir(self)
FileNotFoundError: [Errno 2] No such file or directory: 'test/2019/07/29'

移除文件

使用Path.unlink()来删除文件,但是给出的路径不能是目录,如果是目录则会抛出错误。如果文件不存在,则抛出FileNotFoundError

>>> p = Path('test/file1.py')
>>> p.exists()
True
>>> p.unlink()
>>> p.exists()
False
>>> p = Path('test')
>>> p.unlink()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1287, in unlink
    self._accessor.unlink(self)
IsADirectoryError: [Errno 21] Is a directory: 'test'

>>> p = Path('test/file1.py')
>>> p.exists()
False
>>> p.unlink()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1287, in unlink
    self._accessor.unlink(self)
FileNotFoundError: [Errno 2] No such file or directory: 'test/file1.py'

重命名和移动文件和目录

我们使用Path.rename()重命名文件,其含有参数target,将文件名称替换成target。要保证文件存在,如果文件不存在,则抛出FileNotFoundError

Path.replace()也具有重命名的作用,但是如果原本的文件存在,我们就可以将其无条件替换。

同时,如果target参数给予的是一个路径,我们会将其放在指定的路径下。(但是在有些情况,最后使用重新读出并写入的方式)

我们需要知道的是,虽然更改了名字,但是原来实例化的对象还是没有改变(Python<3.8)。

>>> p = Path('test/file2.py')
>>> p
PosixPath('test/file2.py')
>>> p.exists()
True
>>> p.rename('file1.py')
>>> p
PosixPath('test/file2.py')
>>> p.exists()
False
In [31]: p = Path('bbb.txt')                                                    

In [32]: p.exists()                                                             
Out[32]: True

In [33]: p.replace('Data/bbb.txt') 
In [35]: cd Data                                                                
/home/smith/Data

In [36]: ls                                                                     
bbb.txt    ISO/   notebook/  Python/  Tools/  Vm/

改变模式和权限

>>> p = Path('usa.txt')
>>> p.exists()
True
>>> p.chmod(0o777)
>>> p
PosixPath('usa.txt')
>>> p.stat().st_mode
33279
>>> p.chmod(0o444)
>>> p.stat().st_mode
33060

文件名模式匹配

  • Path.glob(pattern)

    产生当前目录下所有匹配的文件。

    pattern: 通配符,匹配的模式,差不多只是用三种匹配符。 1. ‘*‘匹配0或多个字符 2. ‘?’匹配单个字符 3. ‘[]’匹配制定范围内的字符,如:[0-9]匹配数字。

模式匹配

当我们需要搜索和特定的模式匹配的文件时,在pathlib模块中,提供了Path.glob()方法,我们只使用了三种匹配符。

Path.glob()返回了给定目录下所有匹配的该模式的生成器对象

>>> p
PosixPath('test')
>>> p.glob('*.py')
<generator object Path.glob at 0x7fe32a25bf48>
>>> list(p.glob('*.py'))
[PosixPath('test/file2.py'), PosixPath('test/file1.py')]
>>> list(p.glob('?ile1.py'))
[PosixPath('test/file1.py')]
>>> list(p.glob('file[0-9].py'))
[PosixPath('test/file2.py'), PosixPath('test/file1.py')]

文件名拼接

  • Path.joinpath(*other)

    将other参数中的项目链接在一起

    • other: 多个表示路径的字符串或Path对象

文件名拼接

Path.joinpath()方法只是单纯的将其拼接起来,无论你给other参数多少个对象,他都会以此拼接得到一个路径。

>>> p.joinpath('test1', 'test2')
PosixPath('test/test1/test2')

需要注意的是,如果other参数中含有以/开头的路径,路径将从最后一个以/开头的路径开始拼接

>>> p.joinpath('test2', '/test3')
PosixPath('/test3')

文件的读写操作

  • Path.open(mode=’r’, buffering=-1, encoding=None, errors=None, newline=None)

    用于打开文件,具体参数可以参见观望内置函数open(),因为内容太多了,我懒得写,大多我暂时又用不到。

  • Path.read_bytes()

    以字节对象的形式返回路径指向的文件的二进制内容

  • Path.read_text()

    以字符串形式返回路径指向的文件的解码后的文本内容

  • Path.write_bytes(data)

    将文件以文本模式打开,写入data并关闭

  • Path.write_text(data, encoding=None, errors=None)

    将文件以文本模式打开,写入data并关闭。

    encoding: 编码模式

打开文件

pathlib模块有一个Path.open()的方法,用来打开路径指向的文件,他就跟python内置函数open()函数所做的是一样。

Path.open()含有五个参数,其中mode参数控制了打开的模式, 默认是 ‘r’,也就是打开并读取。


smith@smithgua:~/Desktop$ cat test/file1.py
def test():
   print('hello world')
smith@smithgua:~/Desktop$ python
>>> from pathlib import Path
>>> 
>>> p = Path('test/file1.py')
>>> p
PosixPath('test/file1.py')
>>> p.exists()
True
>>> with p.open() as f:
...     f.read()
... 
"def test():\n   print('hello world')\n"
>>> with p.open(mode='a') as f:
...     f.write('hello world\n')
... 
11
>>> exit()
smith@smithgua:~/Desktop$ cat test/file1.py
def test():
   print('hello world')
hello world

读取文件内容

Pathlib模块提供了读取整个文件的方法,也就是Path.read_bytes()Path.read_text()方法,一种是以字节对象的形式返回文件的二进制内容,一种是以字符串形式返回文件中解码后的文本内容。

与常规的python对文件的操作,使用pathlib方法,我们不需要再打开文件,而是直接对Path对象操作即可

>>> str1 = p.read_bytes()
>>> str1
b"def test():\n   print('hello world')\nhello world\n"
>>> type(str1)
<class 'bytes'>
>>> str2 = p.read_text()
>>> str2
"def test():\n   print('hello world')\nhello world\n"
>>> type(str2)
<class 'str'>

写入文件

Pathlib模块提供了Path.write_bytes()Path.write_text()来写入内容。这两种方法一个是以文件以二进制模式打开,一种是以文本模式打开。

使用Path.write_bytes()方法则需要注意格式,我们的data不是一个字符串形式的。

Path.write_text()含有三个参数,data就是你写入的内容,encoding是你的编码格式。

需要注意的是,我们使用Pathlib模块提供的方法写入内容是直接覆盖的,是不可以选择写入模式的。

smith@smithgua:~/Desktop$ cat test/file1.py
def test():
   print('hello world')
hello world
smith@smithgua:~/Desktop$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pathlib import Path
>>> 
>>> p = Path('test/file1.py')
>>> 
>>> p.write_bytes('hello world')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/smith/anaconda3/lib/python3.7/pathlib.py", line 1207, in write_bytes
    view = memoryview(data)
TypeError: memoryview: a bytes-like object is required, not 'str'
>>> p.write_bytes(b'hello world')
11
>>> exit()
smith@smithgua:~/Desktop$ cat test/file1.py
hello world
smith@smithgua:~/Desktop$ cat test/file1.py
hello worldsmith@smithgua:~/Desktop$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pathlib import Path
>>> p = Path('test/file1.py')
>>> p.write_text('hello python\n')
13
>>> 
>>> exit()
smith@smithgua:~/Desktop$ cat test/file1.py
hello python

与IO模块的结合

虽然pathlib模块提供的读写方法都很强大,但是依然有所不足,所以,与IO模块中对文件的操作进行结合是相当适合的。