经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Python » 查看文章
Python使用os模块实现更高效地读写文件
来源:jb51  时间:2022/7/20 13:09:09  对本文有异议

使用 os.open 打开文件

无论是读文件还是写文件,都要先打开文件。说到打开文件,估计首先想到的就是内置函数 open(即 io.open),那么它和 os.open 有什么关系呢?

内置函数 open 实际上是对 os.open 的封装,在 os.open 基础上增加了相关访问方法。因此为了操作方便,应该调用内置函数 open 进行文件操作,但如果对效率要求较高的话,则可以考虑使用 os.open。

此外 open 函数返回的是一个文件对象,我们可以在此基础上进行任意操作;而 os.open 返回的是一个文件描述符,说白了就是一个整数,因为每一个文件对象都会对应一个文件描述符。

  1. import?os
  2.  
  3. f1?=?open("main.c",?"r")
  4. f2?=?os.open("main.c",?os.O_RDONLY)
  5.  
  6. print(f1.__class__)
  7. print(f2.__class__)
  8. """
  9. <class?'_io.TextIOWrapper'>
  10. <class?'int'>
  11. """

Python 的 open 函数实际上是封装了 C 的 fopen,C 的 fopen 又封装了系统调用提供的 open。

操作系统提供了很多的系统调用,打开文件则是 open,我们看到它返回一个整数,这个整数就是对应的文件描述符。C 的 fopen 封装了系统调用的 open,返回的是一个文件指针。

所以内置函数 open 和 os.open 的区别就更加清晰了,内置函数 open 在底层会使用 C 的 fopen,得到的是一个封装好的文件对象,在此基础上可以直接操作。至于 os.open 在底层则不走 C 的 fopen,而是直接使用系统调用提供的 open,得到的是文件描述符。

os 模块内部的函数基本上都是直接走的系统调用,所以模块名才叫 os。

然后我们使用 os.open 一般需要传递两个参数,第一个参数是文件名,第二个参数是模式,举个栗子:

  1. import?os
  2.  
  3. #?以只读方式打开,要求文件必须存在
  4. #?打开时光标处于文件的起始位置
  5. os.open("main.c",?os.O_RDONLY)
  6.  
  7. #?以只写方式打开,要求文件必须存在
  8. #?打开时光标处于文件的起始位置
  9. os.open("main.c",?os.O_WRONLY)
  10.  
  11. #?以可读可写方式打开,要求文件必须存在
  12. #?打开时光标处于文件的起始位置
  13. os.open("main.c",?os.O_RDWR)
  14.  
  15. #?以只读方式打开,文件不存在则创建
  16. #?存在则不做任何事情,等价于?os.O_RDONLY
  17. #?打开时光标处于文件的起始位置
  18. os.open("main.c",?os.O_RDONLY?|?os.O_CREAT)
  19.  
  20. #?同理?os.O_WRONLY?和?os.O_RDWR?与之类似
  21. os.open("main.c",?os.O_WRONLY?|?os.O_CREAT)
  22. os.open("main.c",?os.O_RDWR?|?os.O_CREAT)
  23.  
  24. #?文件不存在时创建,存在时清空
  25. #?打开时光标处于文件的起始位置
  26. os.open("main.c",
  27. ????????os.O_WRONLY?|?os.O_CREAT?|?os.O_TRUNC)
  28. #?当然读取文件也是可以的
  29. #?比如?os.O_RDONLY?|?os.O_CREAT?|?os.O_TRUNC
  30. #?也是文件存在时清空内容,但是这没有任何意义
  31. #?因为读取的时候将文件清空了,那还读什么?
  32.  
  33. #?文件不存在时创建,存在时追加
  34. #?打开时光标处于文件的末尾
  35. os.open("main.c",
  36. ????????os.O_WRONLY?|?os.O_CREAT?|?os.O_APPEND)
  37.  
  38. #?所以
  39. """
  40. open里面的读模式等价于这里的?os.O_RDONLY
  41. open里面的写模式等价于这里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_TRUNC
  42. open里面的追加模式等价于这里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_APPEND
  43. """

好,打开方式介绍完了,那么怎么读取和写入呢?很简单,读取使用 os.read,写入使用 os.write。

使用 os.read 读取文件

先来看读取,os.read 接收两个参数,第一个参数是文件描述符,第二个参数是要读取多少个字节。

  1. import?os
  2.  
  3. fd?=?os.open("main.c",?os.O_RDONLY)
  4. #?使用?os.read?进行读取
  5. #?这里读取?20?个字节
  6. data?=?os.read(fd,?20)
  7. print(data)
  8. """
  9. b'#include?<Python.h>'
  10. """
  11.  
  12. #?再读取?20?个字节
  13. data?=?os.read(fd,?20)
  14. print(data)
  15. """
  16. b'\n#include?<ctype.h>'
  17. """
  18.  
  19. #?继续读取
  20. data?=?os.read(fd,?20)
  21. #?由于只剩下一个字节
  22. #?所以就读取了一个字节
  23. #?显然此时文件已经读完了
  24. print(data)
  25. """
  26. b'\n'
  27. """
  28.  
  29. #?文件读取完毕之后
  30. #?再读取的话会返回空字节串
  31. print(os.read(fd,?20))??#?b''
  32. print(os.read(fd,?20))??#?b''
  33. print(os.read(fd,?20))??#?b''

所以这就是文件的读取方式,还是很简单的。然后在读取的过程中,我们还可以移动光标,通过 os.lseek 函数。

  • os.lseek(fd, m, 0):将光标从文件的起始位置向后移动 m 个字节;
  • os.lseek(fd, m, 1):将光标从当前所在的位置向后移动 m 个字节;
  • os.lseek(fd, m, 2):将光标从文件的结束位置向后移动 m 个字节;

如果 m 大于 0,表示向后移动,m 小于 0,表示向前移动。所以当第三个参数为 2 的时候,也就是结束位置,那么 m 一般为负数。因为相对于结束位置,肯定要向前移动,当然向后移动也可以,不过没啥意义;同理当第三个参数为 0 时,m 一般为正数,相对于起始位置,肯定要向后移动。

  1. import?os
  2.  
  3. fd?=?os.open("main.c",?os.O_RDONLY)
  4. data?=?os.read(fd,?20)
  5. print(data)
  6. """
  7. b'#include?<Python.h>'
  8. """
  9.  
  10. #?从文件的起始位置向后移动?0?个字节
  11. #?相当于将光标设置在文件的起始位置
  12. os.lseek(fd,?0,?0)
  13. data?=?os.read(fd,?20)
  14. print(data)
  15. """
  16. b'#include?<Python.h>'
  17. """
  18.  
  19. #?设置在结束位置
  20. os.lseek(fd,?0,?2)
  21. print(os.read(fd,?20))??#?b''
  22.  
  23. #?此时就什么也读不出来了

然后我们提一下 stdin, stdout, stderr,含义应该不需要解释了,重点是它们对应的文件描述符分别为 0, 1, 2。

  1. import?os
  2.  
  3. #?从标准输入里面读取?10?个字节
  4. #?没错,此时作用类似于?input
  5. while?True:
  6. ????data?=?os.read(0,?10).strip()
  7. ????print(f"你输入了:",?data)
  8. ????if?data?==?b"exit":
  9. ????????break

我们测试一下:

os.read 可以实现 input 的效果,并且效率更高。另外当按下回车时,换行符也会被读进去,所以需要 strip 一下。然后我们这里读的是 10 个字节,如果一次读不完,那么会分多次读取。在读取文件的时候,也是同理。

  1. from?io?import?BytesIO
  2. import?os
  3.  
  4. fd?=?os.open("main.c",?os.O_RDONLY)
  5. buf?=?BytesIO()
  6.  
  7. while?True:
  8. ????data?=?os.read(fd,?10)
  9. ????if?data?!=?b"":
  10. ????????buf.write(data)
  11. ????else:
  12. ????????break
  13. print(buf.getvalue().decode("utf-8"))
  14. """
  15. #include?<Python.h>
  16. #include?<ctype.h>
  17.  
  18. """

然后 os.read 还可以和内置函数 open 结合,举个栗子:

  1. import?os
  2. import?io
  3.  
  4. f?=?open("main.c",?"r")
  5. #?通过?f.fileno()?即可拿到对应的文件描述符
  6. #?虽然这里是以文本模式打开的文件
  7. #?但只要拿到文件描述符,都可以交给?os.read
  8. print(
  9. ????os.read(f.fileno(),?10)
  10. )??#?b'#include?<'
  11.  
  12. #?查看光标位置
  13. print(f.tell())??#?10
  14.  
  15. #?移动光标位置
  16. #?从文件开头向后移动?5?字节
  17. f.seek(5,?0)
  18. print(f.tell())??#?5
  19. #?os.lseek?也可以实现
  20. os.lseek(f.fileno(),?3,?0)
  21. print(f.tell())??#?3
  22. #?此时会从第?4?个字节开始读取
  23. print(f.read())
  24. """
  25. clude?<Python.h>
  26. #include?<ctype.h>
  27.  
  28. """
  29.  
  30. #?os.lseek?比?f.seek?要强大一些
  31. #?移动到文件末尾,此时没问题
  32. f.seek(0,?2)
  33. print(f.tell())??#?41
  34.  
  35. try:
  36. ????f.seek(-1,?2)
  37. except?io.UnsupportedOperation?as?e:
  38. ????print(e)
  39. """
  40. can't?do?nonzero?end-relative?seeks
  41. """
  42. #?但如果要相对文件末尾移动具体的字节数
  43. #?那么?f.seek?不支持,而?os.lseek?是可以的
  44. print(f.tell())??#?41
  45. os.lseek(f.fileno(),?-1,?2)
  46. print(f.tell())??#?40
  47. #?最后只剩下一个换行符
  48. print(os.read(f.fileno(),?10))??#?b'\n'

是不是很好玩呢?

使用 os.write 写入文件

然后是写入文件,调用 os.write 即可写入。

  1. import?os
  2.  
  3. #?此时可读可写,文件不存在时自动创建,存在则清空
  4. fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC)
  5. #?写入内容,接收两个参数
  6. #?参数一:文件描述符;参数二:bytes 对象
  7. os.write(fd,?b"hello,?")
  8. os.write(fd,?"古明地觉".encode("utf-8"))
  9. #?读取内容
  10. data?=?os.read(fd,?1024)
  11. print(data)??#?b''
  12. #?问题来了,为啥读取不到内容呢?
  13. #?很简单,因为光标会伴随着数据的写入而不断后移
  14. #?这样的话,数据才能不断地写入
  15. #?因此,现在的光标位于文件的结尾处
  16. #?想要查看写入的内容需要移动到开头
  17. os.lseek(fd,?0,?0)
  18. print(os.read(fd,?1024).decode("utf-8"))
  19. """
  20. hello,?古明地觉
  21. """
  22. #?从后往前移动?3?字节
  23. os.lseek(fd,?-3,?2)
  24. print(os.read(fd,?1024).decode("utf-8"))
  25. """
  26. """

以上就是文件的写入,当然它也可以和内置函数 open 结合,通过 os.write(f.fileno(), b"xxx") 进行写入。但是不建议 os.open 和 open 混用,其实工作中使用 open 就足够了。

然后是 stdout 和 stderr,和 os.write 结合可以实现 print 的效果。

  1. import?os
  2.  
  3. os.write(1,?"往?stdout?里面写入\n".encode("utf-8"))
  4. os.write(2,?"往?stderr?里面写入\n".encode("utf-8"))

执行一下,查看控制台:

以上就是 os.write 的用法。

最后是关闭文件,使用 os.close 即可。

  1. import?os
  2. import?io
  3.  
  4. fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC)
  5. #?关闭文件
  6. os.close(fd)
  7.  
  8. #?文件对象也是可以的
  9. f?=?open(r"1.txt",?"r")
  10. os.close(f.fileno())
  11. try:
  12. ????f.read()
  13. except?OSError?as?e:
  14. ????print(e)
  15. """
  16. [Errno?9]?Bad?file?descriptor
  17. """

如果是调用 f.close() 关闭文件,再进行读取的话,会抛出一个 ValueError,提示 I/O operation on closed file。这个报错信息比较明显,不应该在关闭的文件上执行 IO 操作,因为文件对象知道文件已经关闭了,毕竟调用的是自己的 close 方法,所以这个报错是解释器给出的。当然啦,调用 f.close 也会触发 os.close,因为关闭文件最终还是要交给操作系统负责的。

但如果是直接关闭底层的文件描述符,文件对象是不知道的,再使用 f.read() 依旧会触发系统调用,也就是 os.read。而操作系统发现文件已经关闭了,所以会报错:文件描述符有问题,此时就是一个 OSError,报错信息是操作系统给出的。

  1. import?os
  2.  
  3. f?=?open(r"1.txt",?"r")
  4. #?文件是否关闭
  5. print(f.closed)??#?False
  6. os.close(f.fileno())
  7. print(f.closed)??#?False
  8.  
  9. #?所以调用?os.close,文件对象?f?并不知道
  10. #?f.read?依旧会触发系统调用

如果是使用 f.close()。

  1. f?=?open(r"1.txt",?"r")
  2. f.close()
  3. print(f.closed)??#?True

后续执行 IO 操作,就不会再走系统调用了,而是直接抛出 ValueError,原因是文件对象知道文件已经关闭了。

除了 os.close 之外,还有一个 os.closerange,可以关闭多个文件描述符对应的文件。

  1. import?os
  2.  
  3. #?关闭文件描述符为?1、2、3、4?的文件?
  4. os.closerange(1,?5)

该方法不是很常用,了解一下即可。

以上就是使用 os 模块操作文件,它是直接使用操作系统提供的系统调用,所以效率上会比内置函数 open 要高一些。但是工作中还是不太建议使用 os 模块操作文件,使用内置函数 open 就好。

到此这篇关于Python使用os模块实现更高效地读写文件的文章就介绍到这了,更多相关Python os模块读写文件内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号