Python学习笔记(17)-黑马教程-文件,目录与eval函数

文件的基本操作

操作文件的步骤

在计算机中要操作文件的套路非常固定,一共包含三个步骤:

  1. 打开文件;
  2. 读、写文件:是指将文件内容读入内存;将内存内容写入文件;
  3. 关闭文件。

操作文件的函数/方法

在Python中要担任文件需要记住1个函数和3个方法,如下所示:

序号 函数/方法 说明
01 open 打开文件,并返回文件操作对象
02 read 将文件内容读取到内存
03 write 将指定内容写入文件
04 close 关闭文件

其中open函数负责打开文件,并返回文件对象,而read/write/close这三个方法都需要通过文件对象来调用。

read方法——读取文件

  1. open函数的第一个参数是要打开的文件(文件名是区分大小写的),如果文件存在,返回文件操作对象,如果文件不存在,会抛出异常
  2. read文件可以一次性读入并返回文件的所有内容。
  3. close方法负责关闭文件。如果忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问。因此,当我们open()文件后,就在最后面输入close(),对应起来,再写中间部分,这样会避免忘记关闭文件。

需要注意的是,方法执行后,会把文件指针移动到文件的末尾

现在来看一个案例,我们新建一个README.txt文件,如下所示:

1
2
hello python!
hello world!

在同一个目录下新建一个python文件,命名为hm_01_读取文件.py,如下所示:

1
2
3
4
5
6
7
8
9
# 1. 打开文件
file = open("README")
# 2. 读取文件内容
text = file.read()
print(text)
# 3. 关闭文件
file.close()

运行结果如下所示:

1
2
hello python!
hello world!

文件指针

文件指针会标记从哪个位置开始读取数据,第一次打开文件时,通常文件指针会指向文件的开始位置,如下所示:

当执行了read方法后,文件指标会移动到读取内容的末尾,默认情况下会移动到文件末尾,如下所示:

因此,当我们执行了一次read方法后,读取了所有内容,那么再次调用read方法,就无法获取内容,因此此时文件指标已经移到了文件的末尾。现在我们来验证一下,还是改造原来的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 打开文件
file = open("README.txt")
# 2. 读取文件内容
text = file.read()
print(text)
print("-" * 50)
text = file.read()
print(text)
# 3. 关闭文件
file.close()

运行结果如下所示:

1
2
3
Hello, python!
Hello, world!
--------------------------------------------------

从结果中我们可以发现,第二次调用read方法后,没有内容读取。我们还可以看一下读取文件内容的长度,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 打开文件
file = open("README.txt")
# 2. 读取文件内容
text = file.read()
print(text)
print(len(text)) #输出读取文件长度
print("-" * 50)
text = file.read()
print(text)
print(len(text))
# 3. 关闭文件
file.close()

运行结果如下所示:

1
2
3
4
5
6
Hello, python!
Hello, world!
29
--------------------------------------------------
0

从结果也可以发现,第二次读取的长度是0。

打开文件的方式

open在默认的情况下是以只读的方式打开文件,并且返回文件对象,语法如下所示:

1
f = open("文件名", "访问方式")
模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

写入文件

现在我们向README.txt文件中写入HELLO,如下所示:

1
2
3
4
5
6
7
8
# 1. 打开文件
file = open("README.txt", "w")
# 2. 写入文件
file.write("HELLO")
# 3. 关闭文件
file.close()

查看一下README.txt文件,我们使用了w参数来写入文件,如果原文件中有内容,那么就会覆盖,如果没有这个文件,就新建。

现在我们来看一下参数a,这个参数的功能在于以追加的方式写入文件,如下所示:

1
2
3
4
5
6
7
8
# 1. 打开文件
file = open("README.txt", "a")
# 2. 写入文件
file.write("123HELLO")
# 3. 关闭文件
file.close()

现在我们打开文件README.txt,结果为HELLO123HELLO。如果分别在参数r,w,a的后面添加上+号,即r+, w+, a+,那么就会以读写的方式打开文件,这会造成频繁地移动文件指标,影响文件的读写效率,在开发中,更多的是采用只读只写的方式来操作文件。

按行读取文件内容

read方法会默认把文件的所有内容一次性读取到内存,如果文件太大,对内存的占用会非常严重。此时我们可以使用readline方法。

readline方法

readline方法可以一次读取一行内容,方法执行后,会把文件指针移动到下一行,准备再次读取。因此我们在读取大文件时的代码通常是如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
file = open("README.txt")
while True:
text = file.readline()
# 判断是否读取到内容
if not text:
break
print(text)
file.close()

结果运行如下所示:

1
2
3
4
5
Hello1
Hello2
Hello3

文件读写案例——复制文件

在这个案例中,使用代码的方式来实现文件的复制过程。如果我们要复制的源文件是一个小文件,那么我们就可以使用read方法直接把文件的内容全部读取下来,再写入到另外一个文件中,如下所示:

1
2
3
4
5
6
7
8
9
10
11
# 1. 打开文件
file_read = open("README.txt")
file_write = open("README[复制]", "w")
# 2. 读、写
text = file_read.read()
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()

现在在同一个目录下出现了README[复制].txt这个文件,里面的内容与README.txt完全相同。

再看一个案例,这个是案例是复制大文件。

如果要复制大文件,就不能使用read方法,因为这会对内存造成很大的压力,因此可以使用readline方法。

现在我们在源文件README.txt中输入以下内容:

1
2
3
Hello1
Hello2
Hello345

运行复制大文件的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 打开文件
file_read = open("README.txt")
file_write = open("README[复制]", "w")
# 2. 读、写
while True:
# 读取一行内容
text = file_read.readline()
# 判断是否读取到内容
if not text:
break
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()

运行后,打开复制好的文件,README[复制],结果与源文件一样。

文件/目录的常用管理操作

终端/浏览器中可以执行常规的文件/目录操作,例如创建、重命名、删除、改变路径、查看目录内容等,在python中,如果要实现上述功能,通常使用os模块。

Python中的目录操作有这些:

序号 方法及描述
1 os.access(path, mode):检验权限模式
2 os.chdir(path):改变当前工作目录
3 os.chflags(path, flags):设置路径的标记为数字标记。
4 os.chmod(path, mode):更改权限
5 os.chown(path, uid, gid):更改文件所有者
6 os.chroot(path):改变当前进程的根目录
7 os.close(fd):关闭文件描述符 fd
8 os.closerange(fd_low, fd_high):关闭所有文件描述符,从 fd_low (包含) 到 fd_high (不包含), 错误会忽略
9 os.dup(fd):复制文件描述符 fd
10 os.dup2(fd, fd2):将一个文件描述符 fd 复制到另一个 fd2
11 os.fchdir(fd):通过文件描述符改变当前工作目录
12 os.fchmod(fd, mode):改变一个文件的访问权限,该文件由参数fd指定,参数mode是Unix下的文件访问权限。
13 os.fchown(fd, uid, gid):修改一个文件的所有权,这个函数修改一个文件的用户ID和用户组ID,该文件由文件描述符fd指定。
14 os.fdatasync(fd):强制将文件写入磁盘,该文件由文件描述符fd指定,但是不强制更新文件的状态信息。
15 os.fdopen(fd[, mode[, bufsize]]):通过文件描述符 fd 创建一个文件对象,并返回这个文件对象
16 os.fpathconf(fd, name):返回一个打开的文件的系统配置信息。name为检索的系统配置的值,它也许是一个定义系统值的字符串,这些名字在很多标准中指定(POSIX.1, Unix 95, Unix 98, 和其它)。
17 os.fstat(fd):返回文件描述符fd的状态,像stat()。
18 os.fstatvfs(fd):返回包含文件描述符fd的文件的文件系统的信息,像 statvfs()
19 os.fsync(fd):强制将文件描述符为fd的文件写入硬盘。
20 os.ftruncate(fd, length):裁剪文件描述符fd对应的文件, 所以它最大不能超过文件大小。
21 os.getcwd():返回当前工作目录
22 os.getcwdu():返回一个当前工作目录的Unicode对象
23 os.isatty(fd):如果文件描述符fd是打开的,同时与tty(-like)设备相连,则返回true, 否则False。
24 os.lchflags(path, flags):设置路径的标记为数字标记,类似 chflags(),但是没有软链接
25 os.lchmod(path, mode):修改连接文件权限
26 os.lchown(path, uid, gid):更改文件所有者,类似 chown,但是不追踪链接。
27 os.link(src, dst):创建硬链接,名为参数 dst,指向参数 src
28 os.listdir(path):返回path指定的文件夹包含的文件或文件夹的名字的列表。
29 os.lseek(fd, pos, how):设置文件描述符 fd当前位置为pos, how方式修改: SEEK_SET 或者 0 设置从文件开始的计算的pos; SEEK_CUR或者 1 则从当前位置计算; os.SEEK_END或者2则从文件尾部开始. 在unix,Windows中有效
30 os.lstat(path):像stat(),但是没有软链接
31 os.major(device):从原始的设备号中提取设备major号码 (使用stat中的st_dev或者st_rdev field)。
32 os.makedev(major, minor):以major和minor设备号组成一个原始设备号
33 os.makedirs(path[, mode]):递归文件夹创建函数。像mkdir(), 但创建的所有intermediate-level文件夹需要包含子文件夹。
34 os.minor(device):从原始的设备号中提取设备minor号码 (使用stat中的st_dev或者st_rdev field )。
35 os.mkdir(path[, mode]):以数字mode的mode创建一个名为path的文件夹.默认的 mode 是 0777 (八进制)。
36 os.mkfifo(path[, mode]):创建命名管道,mode 为数字,默认为 0666 (八进制)
37 os.mknod(filename[, mode=0600, device]):创建一个名为filename文件系统节点(文件,设备特别文件或者命名pipe)。
38 os.open(file, flags[, mode]):打开一个文件,并且设置需要的打开选项,mode参数是可选的
39 os.openpty():打开一个新的伪终端对。返回 pty 和 tty的文件描述符。
40 os.pathconf(path, name):返回相关文件的系统配置信息。
41 os.pipe():创建一个管道. 返回一对文件描述符(r, w) 分别为读和写
42 os.popen(command[, mode[, bufsize]]):从一个 command 打开一个管道
43 os.read(fd, n):从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd对应文件已达到结尾, 返回一个空字符串。
44 os.readlink(path):返回软链接所指向的文件
45 os.remove(path):删除路径为path的文件。如果path 是一个文件夹,将抛出OSError; 查看下面的rmdir()删除一个 directory。
46 os.removedirs(path):递归删除目录。
47 os.rename(src, dst):重命名文件或目录,从 src 到 dst
48 os.renames(old, new):递归地对目录进行更名,也可以对文件进行更名。
49 os.rmdir(path):删除path指定的空目录,如果目录非空,则抛出一个OSError异常。
50 os.stat(path):获取path指定的路径的信息,功能等同于C API中的stat()系统调用。
51 os.stat_float_times([newvalue]):决定stat_result是否以float对象显示时间戳
52 os.statvfs(path):获取指定路径的文件系统统计信息
53 os.symlink(src, dst):创建一个软链接
54 os.tcgetpgrp(fd):返回与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组
55 os.tcsetpgrp(fd, pg):设置与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组为pg。
56 os.tempnam([dir[, prefix]]):Python3 中已删除。返回唯一的路径名用于创建临时文件。
57 os.tmpfile():Python3 中已删除。返回一个打开的模式为(w+b)的文件对象 .这文件对象没有文件夹入口,没有文件描述符,将会自动删除。
58 os.tmpnam():Python3 中已删除。为创建一个临时文件返回一个唯一的路径
59 os.ttyname(fd):返回一个字符串,它表示与文件描述符fd 关联的终端设备。如果fd 没有与终端设备关联,则引发一个异常。
60 os.unlink(path):删除文件路径
61 os.utime(path, times):返回指定的path文件的访问和修改的时间。
62 os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]):输出在文件夹中的文件名通过在树中游走,向上或者向下。
63 os.write(fd, str):写入字符串到文件描述符 fd中. 返回实际写入的字符串长度

目录操作的函数太多,用到的时候再学习,下面只列出几个简单的案例:

显示某文件夹下的所有文件名

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
C:\Users\20161111>type practice.py
import os
for filename in os.listdir('d:/Software'):
print(filename)
C:\Users\20161111>python practice.py
office_tools
Professional_tools
ProgramTool
SnapGene 3.2.1 Win
system_enhance
windows_iso
谷歌批量翻译

创建某个目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import os
>>> os.mkdir("d:/Software/test")
>>> exit()
C:\Users\20161111>d:
D:\>cd software
D:\software>dir
Volume in drive D is 新加卷
Volume Serial Number is C0C6-2E4F
Directory of D:\software
2018/05/24 09:33 <DIR> .
2018/05/24 09:33 <DIR> ..
2018/05/08 13:46 <DIR> office_tools
2018/05/21 10:30 <DIR> Professional_tools
2018/05/08 13:46 <DIR> ProgramTool
2017/11/22 20:21 <DIR> SnapGene 3.2.1 Win
2018/05/13 21:38 <DIR> system_enhance
2018/05/24 09:33 <DIR> test # 刚刚创建的文件夹
2018/05/11 00:48 <DIR> windows_iso
2017/11/02 10:52 <DIR> 谷歌批量翻译
1 File(s) 0 bytes
10 Dir(s) 49,307,074,560 bytes free

删除某个文件夹

命令rmdir,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import os
>>> os.rmdir('d:/Software/test')
>>> exit()
D:\software>dir
Volume in drive D is 新加卷
Volume Serial Number is C0C6-2E4F
Directory of D:\software
2018/05/25 12:40 <DIR> .
2018/05/25 12:40 <DIR> ..
2018/05/08 13:46 <DIR> office_tools
2018/05/21 10:30 <DIR> Professional_tools
2018/05/08 13:46 <DIR> ProgramTool
2017/11/22 20:21 <DIR> SnapGene 3.2.1 Win
2018/05/13 21:38 <DIR> system_enhance
2018/05/11 00:48 <DIR> windows_iso
2017/11/02 10:52 <DIR> 谷歌批量翻译
1 File(s) 0 bytes
9 Dir(s) 48,131,309,568 bytes free

D:\Software\test这个文件夹删除了 。

重命名某个文件

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\13_文件>ipython
Python 3.6.3 |Anaconda, Inc.| (default, Oct 15 2017, 03:27:45) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import os
In [2]: os.rename("README.txt","TEST.txt")
In [3]: ls
Volume in drive D is 新加卷
Volume Serial Number is C0C6-2E4F
Directory of D:\netdisk\bioinfo.notes\Python\黑马教程笔记\13_文件
2019/06/09 16:51 <DIR> .
2019/06/09 16:51 <DIR> ..
2019/06/09 16:46 <DIR> .idea
2019/06/09 15:39 164 hm_01_读取文件.py
2019/06/09 16:19 229 hm_02_读取文件后文件指针会改变.py
2019/06/09 16:25 129 hm_03_写入文件
2019/06/09 16:37 177 hm_04_分行读取文件.py
2019/06/09 16:41 224 hm_05_复制文件.py
2019/06/09 16:44 347 hm_06_复制大文件.py
2019/06/09 16:46 24 README[复制]
2019/06/09 16:45 24 TEST.txt
8 File(s) 1,318 bytes
3 Dir(s) 52,836,925,440 bytes free

显示当前目录内的所有文件

使用os模块下的listdir命令,如下所示:

1
2
3
4
5
6
7
8
9
10
11
In [4]: os.listdir(".")
Out[4]:
['.idea',
'hm_01_读取文件.py',
'hm_02_读取文件后文件指针会改变.py',
'hm_03_写入文件',
'hm_04_分行读取文件.py',
'hm_05_复制文件.py',
'hm_06_复制大文件.py',
'README[复制]',
'TEST.txt']

判断文件是目录还是文件

使用os模块中的path.isdir命令,如下所示:

1
2
3
4
5
In [5]: os.path.isdir("TEST.txt")
Out[5]: False
In [6]: os.path.isdir(".idea")
Out[6]: True

eval函数

eval()函数可以接受一个字符串,将其当成有效的表达式来求值,并返回计算结果,来看一下面代码的运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [1]: # 基本的数学计算
In [2]: eval("1 + 1")
Out[2]: 2
In [3]: # 字符串重复
In [4]: eval("'*' * 10")
Out[4]: '**********'
In [5]: # 将字符串转换成列表
In [6]: type(eval("[1, 2, 3, 4, 5]"))
Out[6]: list
In [7]: # 将字符串转换成字典
In [8]: type(eval("{'name':'xiaoming', 'age':18}"))
Out[8]: dict

eval案例——计算器

需求如下:

  1. 提示用户输入一个加减乘除混合运行;
  2. 返回过计算结果。

代码如下所示:

1
2
3
input_str = input("请输入算术题: ")
print(eval(input_str))

运行结果如下所示:

1
2
请输入算术题: (5+4)*5
45

不要滥用eval

在开发过程中,不要使用eval来直接转换input结果,现在我们来看一下为什么不要这么做。

我们先来看一下这行代码(由于本人是在windows环境下运行的,因此使用的是dir命令,它等于同Linux环境下的ls命令,这一点与视频中的不一样):

1
__import__('os').system('dir')

上面的这行代码等于:

1
2
import os
os.system("终端命令")

如果执行成功,返回0,执行失败,返回错误信息。现在还看上面的计算器案例,运行后,如果我们输入的内容是__import__('os').system('dir'),我们看一下计算的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
C:\Anaconda3\python.exe D:/netdisk/bioinfo.notes/Python/黑马教程笔记/13_文件/hm_08_eval计算器.py
请输入算术题: __import__('os').system('dir')
驱动器 D 中的卷是 新加卷
卷的序列号是 C0C6-2E4F
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\13_文件 的目录
2019/06/09 19:54 <DIR> .
2019/06/09 19:54 <DIR> ..
2019/06/09 19:57 <DIR> .idea
2019/06/09 15:39 164 hm_01_读取文件.py
2019/06/09 16:19 229 hm_02_读取文件后文件指针会改变.py
2019/06/09 16:25 129 hm_03_写入文件
2019/06/09 16:37 177 hm_04_分行读取文件.py
2019/06/09 16:41 224 hm_05_复制文件.py
2019/06/09 16:44 347 hm_06_复制大文件.py
2019/06/09 19:54 68 hm_08_eval计算器.py
2019/06/09 16:46 24 README[复制]
2019/06/09 16:45 24 TEST.txt
9 个文件 1,386 字节
3 个目录 52,828,184,576 可用字节

现在我们再换一个命令输入,输入__import__('os').system('cd.>a.txt'),结果就会在同一个目录下创建了一个名为a.txt的文本文件。

现在我们再换一个命令,输入__import__('os').system('del a.txt'),此时就会把刚刚创建的那个a.txt文件删除。

此时我们应该就明白了,使用eval()函数直接转换input的结果了,如果用户使用这个函数直接调用os模块中的命令,就会执行任何终端命令,一旦操作出现失误,会造成严重的后果。

参考资料

  1. 黑马Python教程之面向对象