Python学习笔记八1:文件

2019-03-26  本文已影响0人  奋斗在阿尔卑斯的皮卡丘

@[toc]
  本章将进一步,让程序能够与更大的外部世界交互:文件和流

序:编码

  gbk 汉字编码 还有个名字叫cp936,汉字占2个字节,windows默认的 中国用的
  utf-8 万国码 汉字占3字节, python,linux 默认

一、打开文件

  要打开文件,可使用函数open,它位于自动导入模块io中。函数open将文件名作为唯一必不可少的参数,并返回一个文件对象。
open函数的基本语法如下:

  如果当前目录中有一个名为somefile.txt的文本文件,则可像下面这样打开它:

>>> f = open('somefile.txt')

  默认创建在py文件所在的地方。
  如果文件位于其他地方,可指定完整路径。如果找不到文件,就会报错。

>>> f = open(r'C:\Users\JiaNeng\Desktop\20班视频\文件操作及异常\2.txt')
# open函数返回一个File对象。
>>> print(f.name) #C:\Users\JiaNeng\Desktop\20班视频\文件操作及异常\2.txt

这里有几个概念要弄清楚。

>>> path = './test.txt'

  除了单个点,还可以使用两个点表示父文件夹(或上一级文件夹)。

1. 文本模式

  如果要通过写入文本来创建文件,这种调用函数open的方式并不能满足需求。为解决这种问题,可使用函数open的第二个参数。
  调用函数open时,如果只指定文件名, 将获得一个可读取的文件对象。如果要写入文件,必须通过指定模式来显式地指出这一点。函数open参数mode的可能取值有多个。
<center>表 函数open的参数mode的最常见取值 </center>

描述
'r' 读取模式(默认值),文件不存在时会报错
'w' 写入模式,文件存在会清空之前的内容,文件不存在则会新建文件
'x' 独占写入模式,文件存在会报错,文件不存在会新建文件
'a' 附加模式,不清空之前的文件,直接将写入的内容添加到后面
'b' 二进制模式(与其他模式结合使用)
't' 文本模式(默认值,与其他模式结合使用)
'+' 读写模式(与其他模式结合使用)
'r' 显示地指定读取模式的效果与根本不指定模式相同。

  'w'写入模式让你能够写入文件,并在文件不存在时创建它。
  'x'独占地写入模式更进一步,在文件已存在时引发FileExistsError异常。在写入模式下打开文件时,既有内容将被删除(截断),并从文件开头处开始写入;如果要在既有文件末尾继续写入,可使用附加模式。
'a'打开一个文件用于追加。如果该文件已存在,文件指针就会放在文件的结尾。如果该文件不存在,就创建新文件进行写入。
  当参数带上'b'时,表示可以用来读取一个二进制文件。
  '+'可与其他任何模式任何模式结合起来使用,表示既可读取也可写入。例如,要打开一个文本文件进行读写,可使用'r+'。请注意,'r+''w+'之间有个重要差别:后者截断文件,而前者不会这样做。
  默认模式为'rt',这意味着将把文件视为经过编码的Unicode文本,因此将自动执行解码和编码,且默认使用UTF-8编码。要指定其他编码和Unicode错误处理策略,可使用关键字参数encodeing和errors。这还将自动转换换行字符。默认情况下,行以'\n'结尾,读取时将自动替换其他行尾字符('\r'或'\r\n');写入时将'\n'替换为系统的默认行尾字符。(os.linesep)。
  通常,Python使用通用换行模式。在这种模式下,后面将讨论的readlines等方法能够识别所有合法的换行符(\n,\r和\r\n)如果要使用这种模式,同时禁止自动转换,可将关键字参数newline设置为空字符串,如open(name, newline='')。如果要指定只将'\r''\r\n'视为合法的行尾字符,可将参数newline设置为相应的行尾字符。这样,读取时不会对行尾字符进行转换,但写入时将把'\n'替换为指定的行尾字符。
  如果文件包含非文本的二进制数据,如声音剪辑片段或图像,你肯定不希望执行上述自动转换。为此,只需使用二进制模式(如'rb')来禁用与文本相关的功能。

2. 缓冲

  open函数的第3个参数是可选择的,该参数控制文件的缓存。如果该参数赋值为0False,I/O就是无缓存的。如果是1True,I/O就是有缓存的。大于1的整数代表缓存的大小(单位是字节)。-1小于0的整数代表使用默认的缓存大小。
  缓存一般指的是内存,计算机从内存中读取数据的速度远远大于从磁盘读取数据的速度,一般内存大小远小于磁盘大小,内存的速度比较快,但资源比较紧张,所以这里有是否对数据进行缓存的设置。
I/O在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据在内存中驻留,由CPU这个超快的计算核心执行,涉及数据交换的地方通常是磁盘、网络等,因此需要I/O接口。
  比如打开浏览器,访问百度首页,浏览器需要通过网络I/O获取百度网页。浏览器首先会发送数据给百度服务器,告诉它我想要首页的HTML,这个动作是往外发数据,叫Output;随后百度服务器把网页发过来,这个动作是从外面接收数据,叫Input。通常,程序完成I/O操作会有Input和Output两个数据流。

二、文件的基本方法

  本章介绍文件对象的一些基本方法以及其他类似于文件的对象(有时称为流)。类似于文件的对象支持文件对象的一些方法,如支持read或write,或者两者都支持。在开始之前,首先需要了解一下流的概念。
  I/O编程中,流(Stresm)是一个很重要的概念。可以把流想像成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)鎏金内存,Output Stream就是数据从内存流到外面去。浏览网页时,浏览器和服务器之间至少需要建立两根水管,才能既发送数据又接收数据。

1. 读取和写入

  文件最重要的功能就是提供和接收数据。如果有一个名为f的类似于文件的对象,可使用f.write来写入数据,还可使用f.read来读取数据。与Python的其他大多数功能一样,在哪些东西可用作数据方面,也存在一定的灵活性,但在文本和二进制模式下,基本上分别将str和bytes类用作数据。
  每当调用f.write(string)时,你提供的字符串都将写入到文件中既有内容的后面。

>>> f.open(somefile.txt', 'w')
>>> f.write('Hello, ')  #7
>>> f.write('Word!')    6
>>> f.close()

  请注意:使用完文件后,我调用了方法close。
  读取也一样简单,只需告诉流你要读取多少个字符(在二进制模式下是多少字节)

>>> f.open(somefile.txt', 'r')
>>> f.read(4)   #'Hell'
>>> f.read()    #'o, World!'

  首先,指定了要读取4个字符。接下来,没有指定读取字节数时,read方法会读取打开文件中的所有字节。把所有内容都放在字符串里包括换行符\n。读到最后一行,再读就输出空字符串。

2. 使用管道重定向输出

  在bash等shell中,可依次输入多个命令,并使用管道将它们链接起来。

$ cat somefile.txt | python somescript.py | sort

这条管道线包含三个命令。

# somescript.py
import sys
text = sys.stdin.read()
words = text.split()
wordcount = len(words)
print('Wordcount:', wordcount)

3. 随机存取

  在本章中,我将文件都视为,只能按顺序从头到尾读取。实际上,可在文件中移动,之访问感兴趣的部分(称为随机存取)。为此,可使用可使用文件对象的两个方法:seektell
  方法seek(offset, whence=0)将当前位置(执行读取或写入的位置)移到offset和whence指定的地方。
  参数offset指定了字节(字符)数,而参数whence默认为0,这意味着偏移量是相对于文件开头的(偏移量不能为负数)。参数whence还可设置为1或2,其中前者表示相对于当前位置进行移动(偏移量可以为负),而后者表示相对于文件末尾进行移动。返回值为新的位置。
  请看下面示例:

>>> f = open(r'C\text\somefile.txt','w')
>>> f.write('01234567890123456789') #20
>>> f.seek(5, 0)    #5  指针从开头偏移5
>>> f.write('Hello, World!')    #13
>>> f.close()
>>> f = open(r'C\text\somefile.txt','w')
>>> f.read()        #'01234Hello, World!89'

  方法tell()返回当前位于文件的什么位置。

>>> f = open(r'C\text\somefile.txt','w')
>>> f.read(3)   #'012'
>>> f.read(2)   #'34'
>>> f.tell()    #5

4. 读取和写入行

  与其逐个读取流中的字符,不如成行地读取。Python为我们提供了readline()readlines()writelines()等方法用于行操作,例如:

path = './test.txt'
f_name = open(path,'w')
f_name.write('Hello,World!\n')  #13
f_name = open(path,'a')
f_name.write('welcome!')        #8
f_name = open(path,'r')
f_name.readline()           # 'Hello,World!\n'

  要读取一行(从当前位置到下一个分行符的文本),可使用方法readline,换行符为\n
  调用这个方法时,可不提供任何参数(在这种情况下,将读取一行并返回它;也可提供一个非负整数,指定readline最多可读取多少个字符。readline方法如果返回一个空字符串,说明已经读取到最后一行了。
  如果将上面示例的最后一行改为:

f_name.readlines()          #['Hello world!\n','welcome']

  要读取文件中的所有行,并以列表的方式返回它们,可使用方法readlines。列表中的每个字符串就是文本中的每一行,并且换行符也会被输出。
  readlines方法可以传入数值参数,当传入的数值小于等于列表中一个字符串的长度值时,该字符串会被读取;当传入小于等于0的数值时,所有字符都会被读取。例如:

path = './test.txt'
f_name = open(path,'w')
str_list = ['Hello world!\n','welcome!\n', 'welcome!\n']
f_name.writelines(str_list)
f_name.flush()      #刷新:将缓存中的内容写到文件中
# close()           #close也有这种效果,但一般不这么用
f_name = open(path,'r')
f_name.read()       # 'Hello world!\nwelcome!\nwelcome!\n'
f_name = open(path,'r')
f_name.readlines()  # ['Hello world!\n', 'welcome!\n', 'welcome!\n']

  方法writelinesreadlines相反:接受一个字符串列表(实际上,可以是任何序列或可迭代对象),并将这些字符都写入到文件或流中。请注意,写入时不会添加换行符,因此你必须自行添加。另外,没有方法writeline,因为可以使用wirte。

5. 关闭文件

  别忘了调用方法close将文件关闭。关闭文件没有坏处,在有些操作系统和设置中,还可避免无意义地锁定文件以防修改。另外,这样做还可避免用完系统可能指定的文件打开配额。
  对于写入过的文件,一定要将其关闭,因为Python可能缓冲你写入的数据(将数据暂时存储在某个地方,以提高效率)。因此如果程序因某种原因崩溃,数据可能根本不会写入到文件中。如果要重置缓冲,让所做的修改反映到磁盘文件中,但又不想关闭文件,可使用方法flush。然而,需要注意的是,根据所使用的操作系统和设置,flush可能处于锁定考虑而禁止其他正在运行的程序访问这个文件。
  在读写文件的过程中,出现异常的概率还是挺高的,特别对于大文件的读取和写入,出现异常更是家常便饭。在读或写文件的过程中,用try语句捕获可能出现的异常。在捕获异常前有一个动作要执行,就是使用close方法关闭文件。
  要确保文件得以关闭,可使用一条try/finally语句,并在finally子句中调用close

# 在这里打开文件
try: 
    # 将数据写入到文件中
finally:
    file.close()

  如果每次都要这么写,就会很繁琐。实际上,有一条专门为此设计的语句,那就是with语句,它能自动帮我们调用close方法

with open("somefile.txt") as somefile:
    do_something(somefile)

  with语句让你能够打开文件并将其赋给一个变量(这里是somefile)。在语句体中,你将数据写入文件(还可做其他事情)。到达该语句末尾时,将自动关闭文件,即便出现异常也是如此。

(1) 上下文管理器

  with语句实际上是一个非常通用的结构,允许你是用那个所谓的上下文管理器。上下文管理器是支持两个方法的对象:__enter__和__exit__

6. 文件重命名

  在应用程序的过程中,我们可能需要程序帮助我们重命名某个文件的名字,而不是通过手动的方式进行。
  Python的os模块为我们提供了rename方法,即文件重命名。使用这个方法需要导入os模块。  rename方法的语法如下:

os.rename(current_file_name,new_file_name)

  该方法没有返回值。若文件不在当前目录下,则文件名要带上绝对路径。

open(./test1.txt/','w')
os.rename('test1.txt','test2.txt')

  若之前已经创建了名为test1的文件,则将文件名更改为test2;若之前没有创建test1,则先创建test1,再更改名字。

7. 文件删除

  Python的os模块为我们提供了remove方法,即删除文件。
  remove方法的语法如下:

os.remove(file_name)

  若文件不在当前目录下,则文件名要带上绝对路径。
  该方法没有返回值。
  该方法只能删除已经存在的文件,文件不存在就会抛异常。

8. 使用文件的基本方法

  假如文件somefile.txt包含以下文本,可对其执行哪些操作呢?

Welcome to this file
There is nothing here except
This stupid haiku

  首先是read(n)

>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> f.read(7)       #'Welcome'
>>> f.read(4)       #' to '
>>> f.close()

  接下来是read()

>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> print(f.read())
Welcome to this file
There is nothing here except
This stupid haiku
>>> f.close()

  下面是readline()

>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> for i in range(3):
        print(str(i) + ':' + f.readline(), end='')

0:Welcome to this file
1:There is nothing here except
2:This stupid haiku
>>> f.close()

  最后是readlines()

>>> import pprint
>>> pprint.pprint(open(r'C:\Users\MIC\Desktop\somefile.txt').readlines())
['Welcome to this file\n',
 'There is nothing here except\n',
 'This stupid haiku\n']
>>> f.close()

  下面来尝试写入,首先是write(string)

>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt','w')
>>> f.write('this \nis no\nhaiku')  #17
>>> f.close()

  修改后的文本文件

this 
is no
haiku

  最后是writelines(list)

>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> lines = f.readlines()
>>> f.close()
>>> lines[1] = "isn't a\n"
>>> f=open(r'C:\Users\MIC\Desktop\somefile.txt','w')
>>> f.writelines(lines)
>>> f.close()

  再次修改后的文本文件

this 
isn't a
haiku

三、迭代文件内容

  所谓迭代,是指不断重复一个动作,直到这些动作都完成为止。
  一种常见的文件操作是迭代其内容,并在迭代过程中反复采取某种措施。
  在本节的所有示例中,我都将使用一个名为process的虚构函数来表示对每个字符串或行所做的处理,你可以用自己的喜欢的方式实现这个函数。其中,filename也是虚构的。
  更有用的实现包括将数据存储在数据结构中、计算总和、使用模块re替换以及添加行号。

1.每次一个字符(或字节)

  一种最简单的文件内容迭代是在while循环中使用方法read。例如,你可能想遍历文件中的每个字符(在二进制模式下是每个字节),为此可像下面这样做:

with open(filename) as f:
    while True:
    char = f.read(1)
    if not char:break
    process(char)

  如果你每次读取多个字符(字节),可指定要读取的字符(字节)数。
  该示例对写入文件的每个字符都进行循环了。这个程序运行到文件末尾时,read方法会返回一个空字符串,未执行到空字符串前,返回的都是非空字符,表示布尔值为真。

2. 每次一行

  如果处理文本文件时,你通常想做的是迭代其中的行,而不是每个字符。通过使用readline,可像迭代字符一样轻松地迭代行。

with open(filename) as f:
    while True:
        line = f.readline()
        if not line: break
        process(line)

3. 读取所有内容

  如果文件不太大,可一次读取整个文件;为此,可使用方法read并不提供任何参数(将整个文件读取到一个字符串中),也可使用方法readlines(将文件读取到一个字符串列表中,其中每个字符串都是一行)。
  请注意:除进行迭代外,像这样将文件内容读取到字符串或列表中也对完成其他任务很有帮助。
  例如,可对字符串应用正则表达式,还可将列表存储到某种数据结构中供以后使用。
  迭代字符

with open(filename) as f:
    for char in f.read():
        process(char)

  迭代行

with open(filename) as f:
    for line in f.readlines():
        process(line)

4. 使用fileinput实现延迟行迭代

  我们前面介绍过read方法readlines方法,这两个方法不带参数时将读取文件中所有内容,然后加载到内存中。当需要迭代大型文本中的行,此时使用这种方式将占用太多内存,甚至直接使内存溢出,从而导致执行失败。
  当然,你可转而结合使用while循环readline,但在Python中,在可能的情况下,应该首选for循环,使用for循环意味着可以对任务进行分隔操作,而不是一步到位。
  按行读取文件时,若能使用for循环,则称之为懒加载式迭代。你可使用一种名为延迟行迭代的方法—说它延迟是因为它只读取实际需要的文本部分。
  请注意:模块fileinput会负责打开文件,你只需给它提供一个文件名即可。这些操作被封装在input方法内部了。

import fileinput
for line in fileinput.input(filename):
    process(line)
fileinput.input()它帮助迭代多个输入流中的行,它返回一个可在for循环中进行迭代的对象。

5. 文件迭代器

  文件实际上是可迭代的,这意味着可在for循环中直接使用它们来迭代行。

with open(filename) as f:
    for line in f:
        process(line)

  在这些迭代示例中,我都将文件用作了上下文管理器,以确保文件得以关闭。虽然这通常是个不错的主意,但只要不写入文件,就并非一定要这样做。如果你愿意让Python去负责关闭文件,可进一步简化这个示例,如下所示。

for line in open(filename):
    process(line)

  在这里,我没有将打开的文件赋给变量,因此没法显示地关闭它。
  请注意:sys.stdin也是可迭代的。因此要迭代标准输入中的所有行,可像下面这样做:

import sys:
for line in sys.stdin:
    process(line)

  另外,可对迭代器做的事情基本上都可对文件做,如(使用list(open(filename)))将其转换为字符串列表,其效果与使用readlines相同。

>>> f = open('somefile.txt','w')
>>> print('First', 'line', file = f)
>>> print('Second', 'line', file = f)
>>> print('Third', 'and final', file = f)
>>> f.close()
>>> lines = list(open('somefile.txt'))
>>> lines   # ['First line\n', 'Second line\n', 'Third and final\n']
>>> first,second,third = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> first   #'First line\n'
>>> second  #'Second line\n'
>>> third   #'Third and final\n'

  文件内容

First line
Second line
Third and final

  print('a',file=f)将文本输入到file-like对象中,可以是文件,数据流等等,默认是sys.stdout
在这个示例中,需要注意如下几点。

四、StringIO函数

  数据的读取除了通过文件,还可以在内存中进行。Python中的io模块提供了队str操作的StringIO函数
  要把str写入StringIO,我们需要创建一个StringIO,然后像文件一样写入。操作示例如下:

from io import StringIO
io_val = StringIO()
io_val.write('hello')
print('say:',io_val.getvalue())
# say: hello

  getvalue()方法用于获得写入后的str。
  要读取StringIO,还可以用str初始化StringIO,然后像文件一样读取。

from io import StringIO
io_val = StringIO('Hello\nWorld!\nWelcome!')
while True;
    line = io_val.readline()
    if line =='':
        break
    print('line value:',line.strip())
# line value: Hello
# line value: World!
# line value: Welcome!

五、序列化与反序列化

  在运行程序的过程中,所有变量都在内存中,我们把变量从内存中变成可存储或传输的过程称为序列化。我们可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称为反序列化

下面我们介绍Python中序列化和反序列化的方式。

1. 一般序列化与反序列化

  Python中的pickle模块实现了基本数据序列和反序列化。

  pickle模块的基本接口如下:

pickle.dump(obf,file,[,protocol])

  例如:

import pickle
d = dict(name='xiao zhi',num = 1002)
print(pickle.dumps(d))
#b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x08\x00\x00\x00xiao zhiq\x02X\x03\x00\x00\x00numq\x03M\xea\x03u.'
pickle,dumps()方法把任意对象序列化成一个bytes,然后把这个bytes写入文件。也可以使用另一种方法pickle.dumps(),直接把对象序列化后写入一个文件中,程序如下:
try:
    f_name = open('dump.txt','wb')
    pickle.dump(d,f_name)
finally:
    f_name.close()

  打开dump.txt文件,可以看到里面是一堆看不懂的内容,这些都是python保存的对象内部信息。
  既然已经将内容序列化到文件中了,使用文件时就需要把对象从磁盘读到内存。可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化对象;也可以直接用pickle.load()方法从一个对象中直接反序列化对象。从dump.txt文件中将序列化的内容反序列化的代码如下:

import pickle
try:
    f_name = open('dump.txt','rb')
    print('load result:',pickle.load(f_name))
finally:
    f_name.close()

执行结果如下:

load result: {'num':1002, 'name':'xiao zhi'}

  由执行结果看到,变量的内容被正确读取出来了。不过,虽然内容相同,但是对应的变量已经完全不同了。
  注意:pickle的序列化和反序列化只能用于Python,不同版本的python可能彼此都不兼容,因此pickle一般用于保存不重要的数据,也就是不能成功反序列化也没关系的数据。

2. JSON序列化与反序列化

  本节介绍的JSON方式是通用的。
  JSON是一种轻量级的数据交换格式,是基于ECMAScript的一个子集。

  Python3中可以使用json模块对JSON数据进行编码解码,包含以下两个函数。

  在JSON的编码解码过程中,Python的原始类型与JSON类型会相互转换,具体的转化对照如表所示;

<center>Python编码为JSON类型</center>

Python JSON
dict {}
list,tuple []
str string
int or float number
True/False true/false
None null

<center>JSON解码为Python类型</center>

JSON Python Python JSON
{} dict dict {}
[] list list,tuple []
string str str string
number int or float int or float number
true/false True/False True/False true/false
null None None null

  下面是JSON序列化与反序列化的示例:

import json
data = {'num':1002,'name':'xiao zhi'}
json_str = json.dumps(data)
print("Python 原始数据:", data)
print("JSON 对象:", json_str)

  执行结果如下:

{'num':1002,'name':'xiao zhi'}  # Python 原始数据:
{'num':1002,'name':'xiao zhi'}  # JSON 对象:

  接着以上示例,我么可以将一个JSON编码的字符串转换为一个Python数据结构,代码如下:

>>> data2 = jason.loads(json_str)
>>> data2 = json.loads(json_str)
>>> print("data2['name']:",data2['name'])   #data2['name']: xiao zhi
>>> print("data2['num']:",data2['num'])     #data2['num']: 1002

  如果要处理的是文件而不是字符串,就可以使用json.dump()和json.load()编码、解码JSON数据。

# 写入JSON数据
with open('dump.txt','w') as f:
    json.dump(data,f)
# 读取数据
with OPen('dump.txt.','r') as f:
    data = json.load(f)

六、调试

  当我们读取和写入文件时,经常遇到和空白字符相关的问题。这些问题可能很难调试,因为空格、制表符和换行符通常是不可见的,例如:

>>> str_val = '1 2\t 3\n 4 5'
>>> print(str_val)
1 2  3
 4 5

  在这种情况下,Python为我们提供了repr函数。该函数可接收任何对象作为参数,并返回对象的字符串表达形式。

>>> print(repr(str_val))
# '1 2\t 3\n 4 5'

  结果把字符原本输出了。在实际应用中,使用这种方式可以帮助调试。
  另一个经常遇到的问题是不同系统使用不同的字符表示换行。有的系统使用换行符\n表示换行,有的系统使用回车符\r表示换行。如果我们编写的代码在不同系统上使用,这些不一致就可能导致异常。

上一篇下一篇

猜你喜欢

热点阅读