拿来就能用的python 课程
引言
python是很多人入门计算机语言的首选。 但是繁文缛节,很多人从怎么装python开始学起,然后python计算,然后什么是函数,然后什么是类,然后就因为太难放弃了。(说的是不是你?)
没错,今天我会跳着讲。 如何利用python读写文件。 突然想起来 jojo 奇妙冒险里的 岸边露伴的 替身 天堂之门 。 天堂之门的能力就是 能把对手变成一本书,可以供岸边读取,并且可以在上面修改内容。
input output 编程
在计算机领域,I/O 是 Input/Output 的缩写,它指的是输入和输出操作。由于程序和运行时数据都保存在内存中,并由速度极快的 CPU 来执行,因此凡是涉及到与外部设备(如磁盘、网络等)进行数据交换的地方,都需要I/O接口。
理解输入与输出
以你打开浏览器访问新浪首页为例,浏览器这个程序就需要通过网络I/O来获取网页内容:
输出 (Output):浏览器会向新浪服务器发送数据,请求获取首页的HTML内容。这个向外发送数据的动作,就是输出。
输入 (Input):随后,新浪服务器将网页内容发送回来。这个从外部接收数据的动作,就是输入。
通常情况下,程序完成I/O操作会涉及输入和输出两个数据流。当然,也有只使用一个的情况,比如:
从磁盘读取文件到内存,就只有输入操作。
将数据写入磁盘文件,就只是一个输出操作。
“流”的概念
在I/O编程中,流 (Stream) 是一个非常重要的概念。你可以把流想象成一根单向流动的水管,数据就是水管里的水。
输入流 (Input Stream):数据从外部(如磁盘、网络)流入内存。
输出流 (Output Stream):数据从内存流向外部。
以上面浏览网页为例,浏览器和新浪服务器之间至少需要建立两根“水管”,才能同时发送和接收数据。
I/O的速度匹配问题与解决方案
由于CPU和内存的速度远远快于外部设备,因此I/O编程中存在着严重的速度不匹配问题。举个例子:如果要把100MB的数据写入磁盘,CPU可能只需0.01秒就能处理完并输出数据,但磁盘可能需要10秒才能完全接收并写入这100MB数据。面对这种差异,通常有两种处理方式:
- 同步I/O
在同步I/O模式下,CPU会等待I/O操作完成。这意味着程序会暂停执行后续代码,直到所有数据都写入磁盘(例如等待那100MB数据在10秒后写入完成),然后才继续执行。
这就像你去麦当劳点餐,你说“来个汉堡”,服务员告诉你汉堡需要现做,等5分钟。于是你站在收银台前等了5分钟,拿到汉堡后才去逛商场。
- 异步I/O
在异步I/O模式下,CPU不等待。它会告诉磁盘:“您慢慢写,我不着急,我先去干别的事了。”因此,程序可以立即执行后续代码,而不需要停下来等待I/O操作完成。
这就像你去麦当劳点餐,你说“来个汉堡”,服务员告诉你汉堡需要等5分钟,但你可以先去逛商场,等做好了再通知你。这样你就能立刻去做其他事情了。
性能与复杂度的权衡
很明显,使用异步I/O来编写程序,性能通常会远高于同步I/O,因为它能更充分地利用CPU资源。然而,异步I/O的缺点是编程模型更为复杂。你需要解决如何被通知“汉堡做好了”的问题,而通知的方式也各不相同:
如果是服务员过来找到你,这属于回调模式。
如果服务员发短信通知你,你就需要不停地检查手机,这属于轮询模式。
总而言之,异步I/O的实现复杂度要远远高于同步I/O。
值得一提的是,所有的I/O操作能力都是由操作系统提供的。每一种编程语言,包括Python,都会将操作系统提供的底层C语言接口封装起来,方便开发者使用。
文件读写
读写磁盘文件的功能由操作系统提供。现代操作系统为了安全和稳定,不允许普通程序直接访问或操作磁盘硬件。因此,当程序需要读写文件时,它会向操作系统发出请求,让操作系统打开一个文件对象(在Linux/Unix系统中,这通常被称为文件描述符)。随后,程序通过操作系统提供的接口,可以从这个文件对象中读取数据(即读文件),或者将数据写入这个文件对象(即写文件)。
标示符’r’表示读,这样,我们就成功地打开了一个文件。
但是读取并不代表你能看的到
所以我们还要使用 read 语句
调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:
最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
为了保证无论是否出错都能正确地关闭文件,我们可以使用try … finally来实现:
try:f = open('/path/to/file', 'r')print(f.read())
finally:if f:f.close()
Python引入了with语句来自动帮我们调用close()方法:
在文件读取操作中,直接调用 read() 方法会一次性加载文件的全部内容到内存。如果文件非常大(例如10GB),这会导致内存溢出。
安全的文件读取策略
为了避免内存问题,更安全的方法是反复调用 read(size) 方法。这样每次最多读取指定 size 字节的数据,从而控制内存使用。
此外,Python还提供了其他按行读取的方法:
readline(): 每次读取文件中的一行内容。
readlines(): 一次性读取文件的所有内容,并将其按行分解成一个字符串列表返回。
如何选择读取方法
因此,在进行文件读取时,您需要根据实际需求和文件大小来选择最合适的调用方式:
对于小型文件或需要整体处理文件的情况,可以直接使用 read() 或 readlines()。
对于大型文件或需要逐块/逐行处理的情况,应优先考虑使用 read(size) 或 readline(),以避免内存溢出。
如果是配置文件,调用readlines()最方便:
for line in f.readlines():print(line.strip())
file-like Object
像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。
StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
二进制文件
要读取二进制文件,比如图片、视频等等,用’rb’模式打开文件即可:
b’\xff\xd8\xff\xe1\x00\x18Exif\x00\x00…’
字符编码
要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
>>> f = open('/Users/gbk.txt', 'r', encoding='gbk')
>>> f.read()
写文件
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:f.write('Hello, world!')
要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。
细心的童鞋会发现,以’w’模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入’a’以追加(append)模式写入。
StringIO
很多时候,数据读写不一定是文件,也可以在内存中读写。
StringIO顾名思义就是在内存中读写str
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可.
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
getvalue()方法用于获得写入后的str
要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
BytesIO
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
BytesIO实现了在内存中读写bytes,我们创建一个``BytesIO,然后写入一些bytes:
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'
StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。