1. 前言

第一次接触 Python 语言的 IO API 时,是惊艳的。相比较其它语言所提供的 IO 流 API 。

无论是站在使用者的角度还是站在底层设计者的角度,都可以称得上无与伦比。

很多人在学习 JAVA 语言中的 IO 流 API 时,几乎是崩溃的。其 API 太多、API 之间的关系过于复杂。类的层次结构需要花费很多时间才能搞明白。API 设计者未免有炫技之嫌。

Python 的 IO 流操作,才真正应了哪句话:人生苦短,我学 python 。

open( ) 函数 为操作起点,便捷、快速地完成所有操作,绝对算得上轻量级设计的典范,且高度诠释了“高内聚”概念。使用起来颇有“四两拨千金”的轻松。

通过了解 open( ) 函数的参数设计,其开闭设计思想可谓使用到了极致。

2. open( ) 函数

2.1 函数原型

def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
……

2.2 函数功能

打开一个指定位置的文件并返回 IO 流对象。

2.3 函数参数

Tip: open( ) 函数的参数看起来虽然有点多,在使用时,很多参数都可以采用默认设置,它会提供最优的工作方案。

  • file 参数: 指定文件位置。可以是一个字符串描述的文件路径,也可以是一个文件描述符(int 类型)。

    Tip: 当使用字符串描述时,可以是绝对路径,也可以是相对路径。

    绝对路径: 以绝对位置作为路径的起点。 不同的操作系统中会有差异性,windows 以逻辑盘符为绝对起点,Liunx 以 "/" 根目录为绝对起点。

    file=open("d:/guoke.txt")

    Tip: 上述代码运行时,需要保证在系统的 d 盘下有一个名字 "guoke.txt" 的文件。

    相对路径: 所谓相对路径指以某一个已经存在的路径(或叫参照目录、当前目录)做起点。 默认情况下,相对路径以当前项目目录作为参照目录。可以使用 os 模块 中的 getcwd( ) 方法获取当前参照目录的信息。

    import os
    print(os.getcwd())
    # 本代码的测试项目放在 d:\myc 下;项目名称:filedmeo
    # 输出结果
    # D:\myc\filedmeo

    如下代码需要保证在项目目录中存在 " guoke.txt "

    file = open("guoke.txt")
    # 执行时, python 解释器会自动拼接一个完整路径 D:\myc\filedmeo\guoke.txt

    参照目录 可以是不固定的,而是可变的。

    改变相对路径的参考目录:

    import os
    # 把 d 盘作为当前目录
    os.chdir("d:/")
    print(os.getcwd())
    file = open("guoke.txt")
    # python 解释器会从 d 盘根目录下查找 guoke.txt 文件

    描述符: 使用 open( ) 函数打开一个文件后,python 解释器系统会为此文件指定一个唯一的数字标识符。可以用此描述符作为 open( ) 参数。

    file = open("guo_ke.txt")
    # fileno() 获取文件的描述符
    file1 = open(file.fileno())

    Tip: 用户文件的描述符从 3 开始。0,1,2 是系统保留文件描述符。

    • 0:表示标准输入(键盘)设备描述符。
    file = open(0)
    print("请输入一个数字:")
    res = file.readline()
    print("回显:", res)
    '''
    输出结果
    请输入一个数字:
    88
    回显: 88
    '''
    • 1:表示标准输出设备(显示器)描述符。
    file = open(1, "w")
    file.write("you are welcome!")
    #类似于 print("you are welcome!") 的功能
    • 2:表示标准错误输出设备(显示器)描述符。
    file = open(2, "w")
    file.write("you are welcome!")
    #输出文字会以红色亮显
  • mode: 文件操作模式。默认为 "r" ,表示只读模式。

    模式关键字 描述 异常
    'r' 以只读方式打开文件 文件不存时,会抛出 FileNotFoundError 异常
    ‘r+’ 以可读、可写方式打开文件 文件不存时,会抛出 FileNotFoundError 异常
    ‘w’ 以可写方式打开文件 文件不存在时,创建一个字节 0 的空文件
    ‘w+’ 以可写、可读方式打开文件(清空原内容) 文件不存在时,创建一个字节 0 的空文件
    ‘a’ 以追加方式打开文件 文件不存在时,创建一个字节 0 的空文件
    ‘a+’ 以可追加、可读方式打开文件 文件不存在时,创建一个字节 0 的空文件
    ‘t’ 以文本文件格式打开文件 默认
    ‘b’ 以二进制格式打开文件
    ‘x’ 创建空文件并且可写 文件存在时,抛出 FileExistsError 异常

    只要在模式组合中有 'r' 关键字,则文件必须提前存在:

    file = open("guo_ke.txt")
    file = open("guo_ke.txt", 'r')
    file = open("guo_ke.txt", 'rt')
    file = open("guo_ke.txt", 'r+t')

    只要在模式组合中有 ‘w’ 关键字,则文件可以不必预先存在,如果存在,则原文件中内容会被清空。

    # 可写
    file = open("guo_ke.txt", 'w')
    # 可写、可读
    file = open("guo_ke.txt", 'w+')

    只要在模式组合中有 ‘a’ 关键字,则文件可以不必预先存在,如果存在,原文件中内空不会被清空

    # 追加写
    file = open("guo_ke.txt", 'a')
    # 追加写、且可读
    file = open("guo_ke.txt", 'a+')
  • buffering: 设置缓冲策略。可取值为 0、1、>1 。

    • 0: 在二进制模式下关闭缓冲。

    • 1:在文本模式下使用行缓冲。

      行缓冲:以行数据为单位进行缓存。

    • >1 的整数: 指定缓冲区的大小(以字节为单位)。

    如果没有指定 buffering 参数,则会提供默认缓冲策略:

    • 二进制文件使用固定大小的缓冲块。

      在许多系统上,缓冲区的长度通常为 40968192 字节。

    • "Interactive" 文本文件( isatty() 返回 True 的文件)使用行缓冲。其他文本文件使用和二进制文件相同的缓冲策略。

      isatty( ) 方法检测文件是否连接到一个终端设备。

  • encoding: 指定解码或编码文件时使用的编码名称。

    只能用于文本文件。默认使用平台编码。

  • errors: 指定如何处理编码和解码时抛出的错误。可选项如下:

    • strict: 如果存在编码错误,则引发 ValueError 异常。 默认值 None 具有相同的效果。
    • ignore: 忽略错误。有可能会数据丢失。
    • replace: 会将替换标记(例如 '?' )插入有错误数据的地方。
  • newline: 在读或写文本内容时如何处理换行符号。可取值 None,' ','\n','\r' 和 '\r\n'。

    OS 不同,换行符的描述也有差异。Unix 的行结束 '\n'、Windows 中为 '\r\n'

    • 从流中读数据时,如果 newline 为 None,则启用平台约定换行模式。
    • 写入流时,如果 newline 为 None,则写入的任何 '\n' 字符都将转换为系统默认行分隔符 。如果 newline 是 ' ' 或 '\n',则直接写入。如果 newline 是任何其他合法值,则写入的任何 '\n' 字符将被转换为给定的字符串。
  • closefd:

    file = open("guo_ke.txt",closefd=False)
    '''
    输出结果
    Traceback (most recent call last):
    File "D:/myc/filedmeo/文件嵌套.py", line 1, in <module>
    file = open("guo_ke.txt",closefd=False)
    ValueError: Cannot use closefd=False with file name
    '''

    如果通过一个字符串路径描述打开文件, closefd 必须为 True (默认值),否则将引发错误。

    file = open("guo_ke.txt", )
    # 通过 file 文件的描述符打开文件
    file1 = open(file.fileno(), closefd=False)
    file1.close()
    print("先打开文件:", file.closed)
    print("后打开文件:", file1.closed)
    '''
    输出结果
    先打开文件: False
    后打开文件: True
    '''

    当 open file1 文件时设置为 closefd=False ,则当 file1 文件关闭后,file 文件将保持打开状态。

  • opener:可理解为 open( ) 函数是一个高级封装对象,本质是通过 opener 参数接入了一个真正的具有底层文件操作能力的接口。

    import os
    
    def opener(path, flags):
    return os.open(path, flags)
    # 调用 opener('guo_ke.txt','r') 时的参数来自于 open() 的第一个和第二个
    with open('guo_ke.txt', 'r', opener=opener) as f:
    print(f.read())

    默认 opener 参数引用的就是 os.open( ) 方法。

3. 读写操作

调用 open( ) 函数后会返回一个 IO 流对象。IO 流对象中提供了常规的与读写相关的属性和方法。

class IO(Generic[AnyStr]):
#返回文件的读写模式
@abstractproperty
def mode(self) -> str:
pass
#返回文件的名称
@abstractproperty
def name(self) -> str:
pass
#关闭文件
@abstractmethod
def close(self) -> None:
pass
#判断文件是否关闭
@abstractproperty
def closed(self) -> bool:
pass
#返回文件描述符号,每打开一个文件,python 会分配一个唯一的数字描述符号
@abstractmethod
def fileno(self) -> int:
pass
#刷新缓存中的内容
@abstractmethod
def flush(self) -> None:
pass
#是否连接到一个终端设备
@abstractmethod
def isatty(self) -> bool:
pass
# 参数 n 为 -1 或不传递时,一次性读取文件中的所有内容,如果文件内容过多,可分多次读取
# 读取到文件末尾时,返回一个空字符串 ('')
@abstractmethod
def read(self, n: int = -1) -> AnyStr:
pass
# 文件是否可读
@abstractmethod
def readable(self) -> bool:
pass
# 从文件中读取一行;换行符(\n)留在字符串的末尾
# 返回一个空的字符串时,表示已经到达了文件末尾
# 空行使用 '\n' 表示
@abstractmethod
def readline(self, limit: int = -1) -> AnyStr:
pass
# 读取所有行并存储到列表中
# 也可以使用 list(f)
@abstractmethod
def readlines(self, hint: int = -1) -> List[AnyStr]:
pass
# 移动读写光标,改变文件的读写位置
# 通过向一个参考点添加 offset 来计算位置;参考点由 whence 参数指定。
# whence 的 0 值表示从文件开头起算,1 表示使用当前文件位置,2 表示使用文件末尾作为参考点。
# whence 如果省略则默认值为 0,即使用文件开头作为参考点。
@abstractmethod
def seek(self, offset: int, whence: int = 0) -> int:
pass
# 是否可以移动光标
@abstractmethod
def seekable(self) -> bool:
pass
# 返回文件的当前位置
@abstractmethod
def tell(self) -> int:
pass
# 清除内容
@abstractmethod
def truncate(self, size: int = None) -> int:
pass
# 是否可写
@abstractmethod
def writable(self) -> bool:
pass
# 向文件写入内容
@abstractmethod
def write(self, s: AnyStr) -> int:
pass
#向文件写入一行数据
@abstractmethod
def writelines(self, lines: List[AnyStr]) -> None:
pass

调用 open( ) 函数中使用文本模式时返回的是 TextIO 对象,相比较父类,多了几个特定于文本操作的属性。

class TextIO(IO[str]):
# 缓存信息
@abstractproperty
def buffer(self) -> BinaryIO:
pass
# 设置编码
@abstractproperty
def encoding(self) -> str:
pass
# 设备错误处理方案
@abstractproperty
def errors(self) -> Optional[str]:
pass
# 设置行缓存
@abstractproperty
def line_buffering(self) -> bool:
pass
# 换行符的设置方案
@abstractproperty
def newlines(self) -> Any:
pass

3.1 文本文件读操作

  1. 基本操作
file = open("guo_ke.txt", mode='r')
print("读写模式:", file.mode)
print("文件名:", file.name)
print("文件是否关闭:", file.closed)
print("文件描述符号:", file.fileno())
print("文件是否可读", file.readable())
print("是否是标准输入流:", file.isatty())
print("文件是否可写:", file.writable())
print("缓存方案", file.buffer)
print("文件默认编码:", file.encoding)
print("编程错误处理方案", file.errors)
print("是否设置行缓存", file.line_buffering)
print("换行符的设置方案", file.newlines) '''
输出结果
读写模式: r
文件名: guo_ke.txt
文件是否关闭: False
文件描述符号: 3
文件是否可读 True
是否是标准输入流: False
文件是否可写: False
缓存方案 <_io.BufferedReader name='guo_ke.txt'>
文件默认编码: cp936
编程错误处理方案 strict
是否设置行缓存 False
换行符的设置方案 None
'''

cp936 指的是系统的第 936 号编码方案,即 GBK 编码。

  1. 多样化的读方法:

    无论是读还是写时,需要理解一个文件指针(光标)的概念,也可理解为文件位置。读或写时,只能从当前位置向前移动。

    提前准备好一个文本文件,在文件中写入如下内容

You hide in my heart deeply.
Happiness! There is only you and I together time...
With you just I don't want to give anyone the chance.
Honey, can you marry me, I'll marry you!
Don't know love you count is a close reason?
  • read( ) 方法的使用

    file = open("guo_ke.txt", "r")
    print("----------读取所有内容--------------")
    res = file.read()
    print(res)
    print("----------读取部分内容--------------")
    # 重新回到文件头
    file.seek(0)
    res = file.read(100)
    print(res)
    # 关闭文件资源
    file.close()
    '''
    输出结果
    ----------读取所有内容--------------
    You hide in my heart deeply.
    Happiness! There is only you and I together time...
    With you just I don't want to give anyone the chance.
    Honey, can you marry me, I'll marry you!
    Don't know love you count is a close reason?
    ----------读取部分内容--------------
    You hide in my heart deeply.
    Happiness! There is only you and I together time...
    With you just I don
    '''

    这里有一个细节要注意:

    第一次读取完所有文件内容后,读取位置已经移到了文件尾部。继续读取时是不能读到数据的。

    可通过 seek( ) 方法,把光标移到文件头部。

  • readline( ) 方法的使用

file = open("guo_ke.txt", "r")

print("---------读取一行--------")
res = file.readline()
print("数据长度:", len(res))
print(res)
print("-----------限制内容-------------")
res = file.readline(10)
print("数据长度:", len(res))
print(res)
print("-----------以行为单位读取所有数据-------------")
#回到文件头部位置
file.seek(0)
while True:
res = file.readline()
print(res)
if res == "":
break file.close()
'''
输出结果
---------读取一行--------
数据长度: 29
You hide in my heart deeply. -----------限制内容-------------
数据长度: 10
Happiness!
-----------以行为单位读取所有数据-------------
You hide in my heart deeply. Happiness! There is only you and I together time... With you just I don't want to give anyone the chance. Honey, can you marry me, I'll marry you! Don't know love you count is a close reason?
'''

一行一行读取所有内容时,输出时会在行与行之间产生一个空行。原因是行结束符号 'n' 会被当成一个空行输出。

  • readline( ) 还有一个兄弟 readlines() 。把数据以行为单位一次性存储一个列表中.
file = open("guo_ke.txt", "r")

print("-----------把文件中数据以行为单位存储在列表中---------")
res = file.readlines()
print(res)
file.close()
'''
输出结果
-----------把文件中数据以行为单位存储在列表中---------
['You hide in my heart deeply.\n', 'Happiness! There is only you and I together time...\n', "With you just I don't want to give anyone the chance.\n", "Honey, can you marry me, I'll marry you!\n", "Don't know love you count is a close reason?"]
'''

注意使用数据时换行符号的影响。

读取所有行也可以使用 ist(f) 方式。

file = open("guo_ke.txt", "r")
print(list(file))
  • 文件对象支持以行为单位进行迭代操作。
file = open("guo_ke.txt", "r")
print("-----------迭代方式输出文件内容---------")
for f in file:
print(f)
file.close()

3.2 文本文件写操作

如果使用 "w" 模式进行写操作时,会丢失原来数据。如果不希望这样的事情 发生,可使用 "a" 模式对文写操作。

file = open("guo_ke_0.txt", "w")
file.write("this is a test")
# 添加新行
file.write("\n")
file.write("who are you?")
# 把列表中数据一次写入文件
lst = ["food\n", "fish\n", "cat\n"]
file.write("\n")
file.writelines(lst)
file.close()

3.3 编码的问题

对文件同时做读写操作时,请务必保证编码的一致性。

如下面的代码就会出现 UnicodeDecodeError 异常。

file = open("guo_ke_1.txt", mode="w", encoding="utf-8")
file.write("你好!果壳……")
file.close() file_ = open("guo_ke_1.txt", mode="r", encoding="gbk")
res = file_.read()
print(res)
'''
输出结果
Traceback (most recent call last):
File "D:/myc/filedmeo/乱码问题.py", line 6, in <module>
res = file_.read()
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 16: illegal multibyte sequence
'''

3.4 二进制文本件操作

调用 open( ) 函数时使用 mode='rb' 返回的是 BinaryIO 对象。此对象提供了对二进制文件的读写,对二进制文件的读写操作和文本的没有什么太多区别。

文本文件与二进制文本的操作使用一个参数就能灵活切换。

class BinaryIO(IO[bytes]):

    @abstractmethod
def write(self, s: Union[bytes, bytearray]) -> int:
pass

4. 总结

open( ) 函数是一个神奇的存在。无论是对文本文件还是二进进制文件,无论是读还是写,它都能工作的很好。不得不佩服 python 设计者的简洁设计理念。

像通过文件描述符打开文件,使用 opener 参数自定义底层实现,都可称得上神来之笔。

另使用文件后一定要关闭,除了可以直接调用 close( ) 方法外,还可能使用 with 语句,此语法结构能自动调用 close().

with open("guo_ke.txt") as f:
pass

Python 完美诠释"高内聚"概念的 IO 流 API 体系结构的更多相关文章

  1. java IO流 总结

    [-] 1什么是IO 2数据流的基本概念 1 数据流 2 输入流Input  Stream 3 输出流 数据流分类 3 标准IO 命令行参数 标准输入输出数据流 4javaIO层次体系结构 5 非流式 ...

  2. 学习笔记-java IO流总结 转载

    1.什么是IO Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列.Java的I/O流提供了读 ...

  3. 3,Java中的文件IO流

    1,File类 ··· 概念:File对象可以表示一个文件或目录.可以对其进行增删改查. ··· 常用方法:     File f = new File(".");     判断是 ...

  4. Java的IO流以及输入流与输出流的异同

    一:流的基本概念:           Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列.J ...

  5. C++ IO流_数据的旅行之路

    1. 前言 程序中的数据总是在流动着,既然是流动就会有方向.数据从程序的外部流到程序内部,称为输入:数据从程序内部流到外部称为输出. C++提供有相应的API实现程序和外部数据之间的交互,统称这类AP ...

  6. java IO流的API

    常用的IO流API有:[InputStream.OutputStream] [FileInputStream.FileOutputStream] [BufferedInputStream.Buffer ...

  7. 面系那个对象开发原则.高内聚.低耦合+Python安装详细教程+print输出带颜色的方法

    面系那个对象开发原则.高内聚.低耦合 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准.划分摸块的一个准则就是高内聚低耦合. 这是软件工程中的概念,是判断设计好坏的标准,主要是面向OO的设计, ...

  8. python面试_总结01_概念和内置高阶函数

    - 简答题 1.请谈谈Python中is 和 == 的区别(代码演示) is用于比较两个变量是否引用了同一个内存地址,is表示的是对象标识符(object identity),作用是用来检查对象的标识 ...

  9. python 学习笔记12(事件驱动、IO多路复用、异步IO)

    阻塞IO和非阻塞IO.同步IO和异步IO的区别 讨论背景:Linux环境下的network IO. 1.先决条件(几个重要概念) 1.1.用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32 ...

随机推荐

  1. Vue3源码分析之 Ref 与 ReactiveEffect

    Vue3中的响应式实现原理 完整 js版本简易源码 在最底部 ref 与 reactive 是Vue3中的两个定义响应式对象的API,其中reactive是通过 Proxy 来实现的,它返回对象的响应 ...

  2. [USACO19JAN]Train Tracking 2 P

    拿到本题后,可以观察到一个性质,如果出现了 \(c_i \ne c_{i + 1}\) 那么我们一定可以确定一个位置的值,这启示着我们将 \(c_i\) 相同的部分单独拿出来考虑再将最后的答案合并.于 ...

  3. IDEA Debug常用快捷键

    快捷键 介绍 F7 步入:进入到方法内部执行.一般步入自定义的方法.区别于强行步入 F8 步过:不会进入到方法内部,直接执行. F9 恢复程序:下面有断点则运行到下一断点,否则结束程序. Shift+ ...

  4. Google hacker

    转载请注明来源:https://www.cnblogs.com/hookjc/ Google Hacking其实并算不上什么新东西,在早几年我在一些国外站点上就看见过相关的介绍,但是由于当时并没有重视 ...

  5. 2022寒假集训day4

    day4(day5补完的) 继续刷搜索方面的题, 初步了解了序列. T1 迷宫问题 题目描述设有一个 n*n 方格的迷宫,入口和出口分别在左上角和右上角.迷宫格子中分别放 0 和 1 ,0 表示可通, ...

  6. 关于Miller-Rabin与Pollard-Rho算法的理解(素性测试与质因数分解)

    前置 费马小定理(即若P为质数,则\(A^P\equiv A \pmod{P}\)). 欧几里得算法(GCD). 快速幂,龟速乘. 素性测试 引入 素性测试是OI中一个十分重要的事,在数学毒瘤题中有着 ...

  7. Spring-BeanFactory体系介绍

    1 BeanFactory介绍 BeanFactory是Spring中的根容器接口,所有的容器都从从它继承而来,ApplicationContext中对于BeanDefinition的注册,bean实 ...

  8. CoaXPress 接口相机的控制方法--1

    GenICam 介绍 简而言之,GenICam 定义了一个通用的相机接口,使得应用程序的编写.相机的控制可以与具体的型号解耦,这样就可以设计出通用的软件完成对不同相机的控制.我们实际使用的CoaXPr ...

  9. 基于XC7A100T的PCIe千兆电口以太网收发卡

    一.板卡概述 本板卡采用Xilinx公司的Artix7系列的XC7A100T-2FGG484 芯片作为主处理器.包含双路千兆电口网络,双组DDR,PCIeX1 V1.1接口,板卡设计满足工业级要求. ...

  10. Solution -「洛谷 P5236」「模板」静态仙人掌

    \(\mathcal{Description}\)   Link.   给定一个 \(n\) 个点 \(m\) 条边的仙人掌,\(q\) 组询问两点最短路.   \(n,q\le10^4\),\(m\ ...