IO相关概念

IO在计算机中指Input/Output,也就是输入和输出。

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

IO编程中,Stream(流)是一个很重要的概念:

  • Input Stream就是数据从外面(磁盘、网络)流进内存
  • Output Stream就是数据从内存流到外面去

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

  • 第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO
  • 另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO

操作IO的能力都是由操作系统提供的(现在系统不允许普通的程序直接操作磁盘),每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用。

1.文件读写

Python内置了读写文件的函数,用法和C是兼容的。

读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

1.1读文件

要以读文件的模式打开一个文件对象,使用Python内置的open() 函数,传入文件名和标示符:

>>> f = open('/Users/michael/test.txt', 'r')
>>> f.read()
'Hello, world!'   --读到的是一个字符串
>>> f.close()  --关闭文件

如果文件不存在,open() 函数就会抛出一个IOError 的错误,并且给出错误码和详细的信息告诉你文件不存在
如果文件打开成功,接下来,调用read() 方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str 对象表示。

由于文件读写时都有可能产生IOError ,一旦出错,后面的f.close() 就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally 来实现:

try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

但是每次都这么写实在太繁琐,所以,Python引入了with 语句来自动帮我们调用close() 方法:

with open('/path/to/file', 'r') as f:   ---with自动调用close方法
    print(f.read())

read()和readline()/readlines()方法比较:

  • read()会一次性读取全部文件(如果文件有10G,内存就爆了), 要保险起见,可以反复调用read(size) 方法,每次最多读取size个字节的内容。
  • readline() 可以每次读取一行内容
  • 调用readlines() 一次读取所有内容并按行返回list

适用情况:

  • 如果文件很小,read() 一次性读取最方便;
  • 如果不能确定文件大小,反复调用read(size) 比较保险;
  • 如果是配置文件,调用readlines() 最方便

file-like Object:open() 函数返回的这种有个read() 方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read() 方法就行。 StringIO 就是在内存中创建的file-like Object,常用作临时缓冲。

二进制文件:
前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。
要读取二进制文件,比如图片、视频等等,用'rb' 模式打开文件即可:

>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

字符编码:
要读取非UTF-8编码的文本文件,需要给open() 函数传入encoding 参数,例如,读取GBK编码的文件:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'

遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError ,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open() 函数还接收一个errors 参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')

1.2写文件

写文件和读文件是一样的,唯一区别是调用open() 函数时,传入标识符'w' 或者'wb' 表示写文本文件或写二进制文件。

可以反复调用write() 来写入文件,但是务必要调用f.close() 来关闭文件。

当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close() 方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close() 的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with 语句来得保险:

with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')

写入类型:

  • w: 以'w' 模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)
  • a :可以传入'a' 以追加(append)模式写入。

2.StringIO和BytesIO

2.1 StringIO 内存读写str

StringIO顾名思义就是在内存中读写str。
写入

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())  ----getvalue()获取写入后的str
hello world!

读取

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

2.2 BytesIO 内存读写二进制

写入

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

读取

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

3.操作文件和目录

Python程序中执行这些目录和文件的操作, Python内置的os 模块也可以直接调用操作系统提供的接口函数。

查看操作系统类型

>>> import os
>>> os.name # 操作系统类型
'posix'

如果是posix ,说明系统是LinuxUnixMac OS X ,如果是nt ,就是Windows 系统。
提取系统详细信息

>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')

注意uname() 函数在Windows上不提供,也就是说,os 模块的某些函数是跟操作系统相关的。

环境变量
在操作系统中定义的环境变量,全部保存在os.environ 这个变量中,可以直接查看:

>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})

要获取某个环境变量的值,可以调用os.environ.get('key')

>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'

操作文件和目录
**(1)操作目录 **
os.path.abspath() 绝对路径
os.path.split() 拆分目录
os.mkdir() 创建目录
os.rmdir() 删除目录
操作文件和目录的函数一部分放在os 模块中,一部分放在os.path 模块中,这一点要注意一下。查看、创建和删除目录可以这么调用:

# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')

这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。路径的拆分、合并不要直接去操作字符串,而是利用函数去操作(原因:不同的系统,路径的显示样式不太一样)

  • 把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join() 函数,这样可以正确处理不同操作系统的路径分隔符。
  • 要拆分路径时,也不要直接去拆字符串,而要通过os.path.split() 函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')

os.path.splitext() 可以直接让你得到文件扩展名,很多时候非常方便:

>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')

(2)操作文件
os.rename()
os.remove() 删除文件

# 对文件重命名:
>>> os.rename('test.txt', 'test.py')   --test.txt是原文件
# 删掉文件:
>>> os.remove('test.py')

过滤文件: 要列出所有的.py 文件,也只需一行代码

>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']

4.序列化 picking

把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

Python提供了pickle模块来实现序列化。

1.序列化方法 pickle.dumps() pickle.dump()

pickle.dumps() 方法把任意对象序列化成一个bytes ,然后,就可以把这个bytes 写入文件。

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

另一个方法pickle.dump() 直接把对象序列化后写入一个file-like Object:

>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
2.反序列化方法 pickle.load()

当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes ,然后用pickle.loads() 方法反序列化出对象,也可以直接用pickle.load() 方法从一个file-like Object 中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:

>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

序列化json 待学习

https://ceshiren.com/t/topic/27721

python文件操作

在程序中操作文件和使用图形界面操作文件的过程基本一致,都要进行找到文件位置,打开文件,读写文件,关闭文件等操作。

1.打开文件

完整格式:open(filename, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
简化格式: open(filename, mode='r', encoding=None);

  • filename: 必需,指定打开文件的路径(相对或者绝对路径);
  • mode: 可选,文件打开模式,默认为r只读模式;
  • encoding: 一般使用 utf8;

说明:Python 使用 open 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。

2.关闭文件

close() 方法用于关闭一个已打开的文件
关闭后的文件不能再进行读写操作, 否则会触发 ValueError 错误。
可以调用多次。

格式: fileObject.close()

3.写入文件

(1)fileObject.write( str ): 用于向文件中写入指定字符串。
如果文件打开模式为b ,则要将字符串转换成 bytes 类型的二进制字符串,函数返回成功写入数据的长度。
(2)fileObject.writelines(seq):用于向文件中写入一序列的字符串。
这一序列字符串可以是由迭代对象产生的,如一个字符串列表。
注意:不要被方法名所迷惑,如果每个元素独占一行,需要在数据后指定换行符 \n 。

4.读取文件

(1)fileObject.read(size=-1):用于从文件读取指定的字节数,如果未给定或为负则读取所有。
注意光标的位置,连续读取文件没有关闭的时候,光标会随着读取而向后移动,不会回到默认起始位置;
(2)fileObject.readline(size=-1): 用于从文件读取整行,包括 \n 字符。如果指定了一个非负数的参数,则返回指定大小的字节数,包括 \n 字符。
该方法默认读取一行,如果指定了长度,会读取这一行中的给定长度,并且如果文件不关闭,光标也不会回到默认起始位置,再次读取会从光标所在位置读取这一行剩下的内容;
(3)fileObject.readlines():用于读取所有行(直到结束符 EOF)并返回列表。