06 . Python3入门之IO编程(文件操作)

IO编程简介

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

比如你打开浏览器,访问新浪首页,浏览器这个程序就需要通过网络IO获取新浪的网页。浏览器首先会发送数据给新浪服务器,告诉它我想要首页的HTML,这个动作是往外发数据,叫Output,随后新浪服务器把网页发过来,这个动作是从外面接收数据,叫Input。所以,通常,程序完成IO操作会有Input和Output两个数据流。当然也有只用一个的情况,比如,从磁盘读取文件到内存,就只有Input操作,反过来,把数据写到磁盘文件里,就只是一个Output操作。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和新浪服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。

由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

同步和异步的区别?

是否等待IO执行的结果。好比你去麦当劳点餐,你说“来个汉堡”,服务员告诉你,对不起,汉堡要现做,需要等5分钟,于是你站在收银台前面等了5分钟,拿到汉堡再去逛商场,这是同步IO。

你说“来个汉堡”,服务员告诉你,汉堡需要等5分钟,你可以先去逛商场,等做好了,我们再通知你,这样你可以立刻去干别的事情(逛商场),这是异步IO。

很明显,使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。

操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。我们后面会详细讨论Python的IO编程接口。

输出格式美化

# Python有两种输出值的方式,表达式语句和print()函数
# 第三种方式是使用文件对象的write()方法,标准输出文件可以用sys.stdout引用
# 如果你希望输出的形式更加多样,可以使用str.format()函数格式化输出值
# 如果你希望输出值都转换成字符串,可以使用repr()或str()函数实现

# str:  函数返回一个用户易读的表达形式
# repr():  产生一个解释器易读的表达形式

例如

# 简单应用
name = '幽梦'
age = 18
sex = '男'

msg = F'姓名: {name}, 性别: {age} , 年龄: {sex}'  # 大写字母也可以
print(msg)

# 任意表达式

print(f'{3*21}')

name = 'flying'
print(f'全部大写: {name.upper()}')

# 字典也可以
teacher = {'name':'幽梦','age':18}
msg = f"The student is {['name']}, age{teacher['age']}"
print(msg)  # The comedian is 太白金星, aged 18

# 列表也行
li1 = ['幽梦',18]
msg = f'姓名:{li1[0]},年龄:{li1[1]}.'
print(msg)


# 也可以插入表达式
def sum_a_b(a,b):
    return a + b
a = 1
b = 2
print('求和的结果为' + f'{sum_a_b(a,b)}')


# 多行f
name = 'youmen'
age = 18
sex = '男'

speaker = f'Hi {name}.'\
          f'You are {age} years old.'\
          f'You are a {sex} guy!'
print(speaker)

str.format()的基本使用如下

print('{}网址: "{}!"'.format('菜鸟教程','www.YouMen.com'))
菜鸟教程网址: "www.YouMen.com!"

# 括号及其里面的字符(称作格式化字段)将被format()中的参数替换.
# 在括号中的数字用于指向传入对象在format()中的位置,如下所示:
>>> print('{0}和{1}'.format('Google','baidu'))
Google和baidu

# 如果在format()中使用了关键字参数,那么他们的值会指向使用该名字的参数.

print('站点列表{0},{1},和{other}'.format('Google','baidu.com',other='TaoBao'))
站点列表Google,baidu.com,和TaoBao

>>> import math
>>> print('常亮p1的值近似为: {}.'.format(math.pi))
常亮p1的值近似为: 3.141592653589793.

# 可选项:和格式标识符可以跟着字段名,这就允许对值进行更好的格式化,下面例子将Pi保留到小数后三位.
>>> print('常亮PI的值近似为{0:.3f}。'.format(math.pi))
常亮PI的值近似为3.142。

# 在:后传入一个整数,可以保证该域至少有那么多的宽度,对于美化表格很有用
>>> table={'Google':1,'YouMen':2,'Baidu':3}
>>> for name, number in table.items():
...     print('{0:10} ==> {1:10d}'.format(name, number))
... 
Google     ==>          1
YouMen     ==>          2
Baidu      ==>          3

# 如果你有一个很长的格式化字符串,而你不想将他们分开,那么在格式化时通过变量名而非位置会是很好的事情.
# 最简单的就是传入一个字典,然后使用方括号[]来访问键值.
>>> table = {'Google': 1, 'Runoob': 2, 'Taobao': 3}  
>>> print('Runoob: {0[Runoob]:d}; Google: {0[Google]:d}; Taobao: {0[Taobao]:d}'.format(table))  
Runoob: 2; Google: 1; Taobao: 3

旧式字符串格式化

  • %操作符也可以实现字符串格式化,他将左边的参数作为sprintf()式的格式化字符串,而将右边的代入,然后返回格式化的字符串,例如:
import math
print('常量PI的值近似为: %5.3f。'%math.pi)

常量PI的值近似为: 3.142。

读取键盘输入

Python提供了input内置函数从标准输入读入一行文本,默认的标准输入是键盘.
input可以接受一个Python表达式作为输入,并将运算结果返回.

str = input("请输入:")
print("你输入的内容是:",str)

请输入:
zhou

文件读写

读写文件是最常见的IO操作,Python内置了读写文件的函数,用法和C是兼容的.

读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

open()将会返回一个file对象,基本语法格式如下

open(filename,mode)

filename: 包含了你要访问的文件名称的字符串值
mode: 决定了打开文件的模式: 只读,写入,追加等,所有可取值见如下的完全列表,这个参数是非强制的,默认文件的访问模式为只读(r)
不同模式打开文件的完全列表:

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

下图很好的总结了这几种模式:

null

模式 r r+ w w+ a a+
+ + + +
+ + + + +
创建 + + + +
覆盖 + +
指针在开始 + + + +
指针在结尾 + +

Open()方法

Python open() 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。或者文件不存在抛出IOError错误.
注意: 使用open()方法一定要保证关闭文件对象,即调用close()方法
Open()函数常用形式是接受两个参数: 文件名(file)和模式(mode)

如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示。

open(file,mode='r')
# 完整语法格式为:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

参数说明:
* file: 必需,文件路径(相对或者绝对路径)。
* mode: 可选,文件打开模式
* buffering: 设置缓冲
* encoding: 一般使用utf8
* errors: 报错级别
* newline: 区分换行符
* closefd: 传入的file参数类型
* opener:

mode参数有:

模式 描述
t 文本模式 (默认)。
x 写模式,新建一个文件,如果该文件已存在则会报错。
b 二进制模式。
+ 打开一个文件进行更新(可读可写)。
U 通用换行模式(Python 3 不支持)。
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

默认为文本模式,如果要以二进制模式打开,加上b

以下实例将字符串写入到文件foo.txt中:

# 打开一个文件
f = open("test.txt","w",encoding="utf-8")
f.write("hello world! \n 世界你好!")

# 关闭打开的文件
f.close

cat test.txt   
hello world!   
 世界你好!


# 在实际编程中,我们经常忘记关闭已经打开的文件对象,这样就会导致系统资源的浪费。
# 使用 with 语句来管理上下文操作之后,python内部会自动关闭并释放文件资源。
# 操作只需要为文件句柄起个别名为f,下面直接使用即可
# 打开一个文件
with open('test.txt','w',encoding="utf-8") as you:
    li = ['hello\n','world\n']
    you.writelines(li)
print("结束")

cat test.txt
hello
world

文件对象的方法
1. f.read()

为了读取一个文件的内容,调用 f.read(size), 这将读取一定数目的数据, 然后作为字符串或字节对象返回。
size 是一个可选的数字类型的参数。 当 size 被忽略了或者为负, 那么该文件的所有内容都将被读取并且返回。
以下实例假定文件 test.txt 已存在(上面实例中已创建)

f = open("test.txt","r")
str = f.read()
print(str)
f.close()

hello
world

2. f.readline()

f.readline()会从文件中读取单独的一行,换行符为'\n',f.readline()如果返回一个空字符串,说明已经读取到最后一行.

f = open("test.txt","r")
str = f.readline()
print(str)
f.close()

hello

3. f.readlines()

f.readlines()将返回该文件中包含的所有行
如果设置可选参数sizehint,则读取指定长度的字节,并且将这些字节按行分割.

f = open("test.txt","r")
str = f.readlines()
print(str)
f.close()

['hello\n', 'world\n']

另一种方法就是迭代一个文件对象然后读取每行

for line in f:
    print(line,end='')
f.close()

hello
world
# 这个方法很简单, 但是并没有提供一个很好的控制。 因为两者的处理机制不同, 最好不要混用。

4. f.write()
f.write(string)将string写入到文件中,然后返回写入的字符数.

f = open("test.txt","w",encoding="utf-8")
str1 = ("www.youmen.com",100)
s = str(str1)
# 如果不是字符串类型,需要先将其转换一下,否则执行报错.
num = f.write(s)
print(num)
f.close()

23
# f.tell()
# f.tell()返回文件对象当前所处的位置,他是从文件开头开始算起的字节数

5. f.seek()

# 如果要改变文件当前的位置, 可以使用 f.seek(offset, from_what) 函数。
# from_what 的值, 如果是 0 表示开头, 如果是 1 表示当前位置, 2 表示文件的结尾,例如:
# seek(x,0) : 从起始位置即文件首行首字符开始移动 x 个字符
# seek(x,1) : 表示从当前位置往后移动x个字符
# seek(-x,2):表示从文件的结尾往前移动x个字符
# from_what 值为默认为0,即文件开头。下面给出一个完整的例子:
>>> f = open('/tmp/foo.txt', 'rb+')  
>>> f.write(b'0123456789abcdef')  
16  
>>> f.seek(5)     # 移动到文件的第六个字节  
5  
>>> f.read(1)  
b'5'  
>>> f.seek(-3, 2) # 移动到文件的倒数第三字节  
13  
>>> f.read(1)  
b'd'

6. f.close()

# 在文本文件中(那些打开文件的模式是没有b的),只会相对于文件起始位置进行定位.
# 当你处理一个文件后,调用f.close()来关闭文件并释放系统的资源,如果尝试在调用该文件,则会抛出异常.
# 所以当处理文件对象时,使用with关键字是非常好的方式,结束后,会帮你正确的关闭文件,写起来比try - finally语句块要简短
>>> with open('/tmp/foo.txt', 'r') as f:  
...     read_data = f.read()  
>>> f.closed  
True

7. pickle模块

# Python的pickle模块实现了基本的数据序列和反序列化
# 通过pickle模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储
# 通过pickle模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象

# 基本接口
pickle.dump(obj, file, [,protocol])

# 有了pickle这个对象,就能对file以读取的形式打开:
x = pickle.load(file)

# 从file中读取一个字符串,并将它重构成原来的python对象.
# file: 类文件对象,有read()和readline()接口
StringIO

很多时候,数据读写不一定是文件,也可以是内存中读写.StringIO顾名思义就是内存中读写str,要把str写入StringIO,我们需要先创建一个StringIO,然后像文件一样写入即可:
Example1

Example2

import os   
with open('a.txt', 'r', encoding='utf-8') as read_f, open(   
'.a.txt.swap','w', encoding='utf-8') as write_f:   
    
r_data=read_f.read() 					# 全部读入内存,如果文件很大,会很卡   
w_data=r_data.replace('world','python') 	# 在内存中完成修改   
    
 write_f.write(w_data) 			# 一次性写入新文件   
os.remove('a.txt')   
os.rename('.a.txt.swap','a.txt')  

file对象

file 对象使用 open 函数来创建,下表列出了 file 对象常用的函数:

序号 方法及描述
1 file.close()关闭文件。关闭后文件不能再进行读写操作。
2 file.flush()刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。
3 file.fileno()返回一个整型的文件描述符(file descriptor FD 整型), 可以用在如os模块的read方法等一些底层操作上。
4 file.isatty()如果文件连接到一个终端设备返回 True,否则返回 False。
5 file.next()Python 3 中的 File 对象不支持 next() 方法。返回文件下一行。
6 file.read([size])从文件读取指定的字节数,如果未给定或为负则读取所有。
7 file.readline([size])读取整行,包括 "\n" 字符。
8 file.readlines([sizeint])读取所有行并返回列表,若给定sizeint>0,返回总和大约为sizeint字节的行, 实际读取值可能比 sizeint 较大, 因为需要填充缓冲区。
9 file.seek(offset[, whence])移动文件读取指针到指定位置
10 file.tell()返回文件当前位置。
11 file.truncate([size])从文件的首行首字符开始截断,截断文件为 size 个字符,无 size 表示从当前位置截断;截断之后后面的所有字符被删除,其中 Widnows 系统下的换行代表2个字符大小。
12 file.write(str)将字符串写入文件,返回的是写入的字符长度。
13 file.writelines(sequence)向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。
posted @ 2019-12-05 00:57  常见-youmen  阅读(433)  评论(0编辑  收藏  举报