Python 文件管理

找到一篇很是好的文章,记录下html

原贴:https://www.cnblogs.com/yyds/p/6186621.htmlpython

本节内容:


  1. I/O操做概述
  2. 文件读写实现原理与操做步骤
  3. 文件打开模式
  4. Python文件操做步骤示例
  5. Python文件读取相关方法
  6. 文件读写与字符编码

1、I/O操做概述


I/O在计算机中是指Input/Output,也就是Stream(流)的输入和输出。这里的输入和输出是相对于内存来讲的,Input Stream(输入流)是指数据从外(磁盘、网络)流进内存,Output Stream是数据从内存流出到外面(磁盘、网络)。程序运行时,数据都是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方(一般是磁盘、网络操做)就须要IO接口。linux

那么这个IO接口是由谁提供呢?高级编程语言中的IO操做是如何实现的呢?sql

操做系统是个通用的软件程序,其通用目的以下:编程

  • 硬件驱动
  • 进程管理
  • 内存管理
  • 网络管理
  • 安全管理
  • I/O管理

操做系统屏蔽了底层硬件,向上提供通用接口。所以,操做I/O的能力是由操做系统的提供的,每一种编程语言都会把操做系统提供的低级C接口封装起来供开发者使用,Python也不例外。windows

2、文件读写实现原理与操做步骤


1. 文件读写实现原理

文件读写就是一种常见的IO操做。那么根据上面的描述,能够推断python也应该封装操做系统的底层接口,直接提供了文件读写相关的操做方法。事实上,也确实如此,并且Java、PHP等其余语言也是。api

那么咱们要操做的对象是什么呢?咱们又如何获取要操做的对象呢?安全

因为操做I/O的能力是由操做系统提供的,且现代操做系统不容许普通程序直接操做磁盘,因此读写文件时须要请求操做系统打开一个对象(一般被称为文件描述符--file descriptor, 简称fd),这就是咱们在程序中要操做的文件对象。ruby

一般高级编程语言中会提供一个内置的函数,经过接收"文件路径"以及“文件打开模式”等参数来打开一个文件对象,并返回该文件对象的文件描述符。所以经过这个函数咱们就能够获取要操做的文件对象了。这个内置函数在Python中叫open(), 在PHP中叫fopen(),网络

2. 文件读写操做步骤

不一样的编程语言读写文件的操做步骤大致都是同样的,都分为如下几个步骤:

1)打开文件,获取文件描述符 2)操做文件描述符--读/写 3)关闭文件

只是不一样的编程语言提供的读写文件的api是不同的,有些提供的功能比较丰富,有些比较简陋。

须要注意的是:文件读写操做完成后,应该及时关闭。一方面,文件对象会占用操做系统的资源;另一方面,操做系统对同一时间能打开的文件描述符的数量是有限制的,在Linux操做系统上能够经过ulimit -n 来查看这个显示数量。若是不及时关闭文件,还可能会形成数据丢失。由于我将数据写入文件时,操做系统不会马上把数据写入磁盘,而是先把数据放到内存缓冲区异步写入磁盘。当调用close方法时,操做系统会保证把没有写入磁盘的数据所有写到磁盘上,不然可能会丢失数据。

3、文件打开模式


咱们先来看下在Python、PHP和C语言中打开文件的函数定义

Python
# Python2
open(name[, mode[, buffering]])
# Python3
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

PHP

resource fopen ( string $filename , string $mode [, bool $use_include_path = false [, resource $context ]] )

C语言

int open(const char * pathname, int flags);

 

会发现以上3种编程语言内置的打开文件的方法接收的参数中,除了都包含一个“文件路径名称”,还会包含一个mode参数(C语言的open函数中的flags参数做用类似)。这么mode参数定义的是打开文件时的模式,常见的文件打开模式有:只读、只写、可读可写、只追加。不一样的编程语言中对文件打开模式的定义有些微小的差异,咱们来看下Python中的文件打开模式有哪些。

文件打开模式 描述
r 以只读模式打开文件,并将文件指针指向文件头;若是文件不存在会报错
w 以只写模式打开文件,并将文件指针指向文件头;若是文件存在则将其内容清空,若是文件不存在则建立
a 以只追加可写模式打开文件,并将文件指针指向文件尾部;若是文件不存在则建立
r+ 在r的基础上增长了可写功能
w+ 在w的基础上增长了可读功能
a+ 在a的基础上增长了可读功能
b 读写二进制文件(默认是t,表示文本),须要与上面几种模式搭配使用,如ab,wb, ab, ab+(POSIX系统,包括Linux都会忽略该字符)

思考1: r+、w+和a+均可以实现对文件的读写,那么他们有什么区别呢?

  • r+会覆盖当前文件指针所在位置的字符,如原来文件内容是"Hello,World",打开文件后写入"hi"则文件内容会变成"hillo, World"
  • w+与r+的不一样是,w+在打开文件时就会先将文件内容清空,不知道它有什么用
  • a+与r+的不一样是,a+只能写到文件末尾(不管当前文件指针在哪里)

思考2: 为何要定义这些模式呢?为何不能像咱们用word打开一篇文档同样既能够读,又能够写,还可修改呢?

关于这个问题,我查了不少资料,也没找到很权威的说明。在跟同行朋友交流过程当中,发现你们主要有两种观点:

  • 跟安全有关,有这种观点的大部分是作运维的朋友,他们认为这就像linux上的rwx(读、写、执行)权限。
  • 跟操做系统内核管理I/O的机制有关,有这种观点的大部分是作C开发的,特别是与内核相关的开发人员。为了提升读写速度,要写入磁盘的数据会先放进内存缓冲区,以后再回写。因为可能会同时打开不少文件,当要回写数据时,须要遍历以打开的文件判断是否须要回写。他们认为若是打开文件时指定了读写模式,那么须要回写时,只要去查找以“可写模式”打开的文件就能够了。

4、Python文件操做步骤示例


咱们来读取这样一个文本文件:song.txt,该文件的字符编码为utf-8。

匆匆那年咱们 究竟说了几遍 再见以后再拖延
惋惜谁有没有 爱过不是一场 七情上面的雄辩
匆匆那年咱们 一时匆忙撂下 难以承受的诺言
只有等别人兑现

1. 菜鸟实现(只是实现功能):

Python3实现:

# 第一步:(以只读模式)打开文件
f = open('song.txt', 'r', encoding='utf-8')

# 第二步:读取文件内容
print(f.read())

# 第三步:关闭文件
f.close()

 

这里说下Python2的实现

# 第一步:(以只读模式)打开文件
f = open('song.txt', 'r')

# 第二步:读取文件内容
print(f.read().decode('utf-8'))

# 第三步:关闭文件
f.close()

 

说明:
Python3中已经内置对Unicode的支持,字符串str已是真正的Unicode字符串。也就是说Python3中的文件读取方法已经自动完成了解码处理,所以无需再手动进行解码,能够直接将读取的文件中的内容进行打印;Python2中的字符串str是字节串,读取文件获得的也是字节串,在打印以前应该手动将其解码成Unicode字符串。关于这部分的说明,能够参考以前这篇文章<<再谈Python中的字符串与字符编码>>

2. 中级实现

在实现基本功能的前提下,考虑一些可能的意外因素。由于文件读写时都有可能产生IO错误(IOError),一旦出错,后面包括f.close()在内的全部代码都不会执行了。所以咱们要保证文件不管如何都能被关闭。那么能够用try...finally来实现,这实际上就是try...except..finally的简化版(咱们只用Python3来进行示例演示):

f = ''
try:
    f = open('song.txt', 'r', encoding='utf-8')
    print(f.read())
    num = 10 / 0
finally:
    print('>>>>>>finally')
    if f:
        f.close()

 

输出结果:

匆匆那年咱们 究竟说了几遍 再见以后再拖延
惋惜谁有没有 爱过不是一场 七情上面的雄辩
匆匆那年咱们 一时匆忙撂下 难以承受的诺言
只有等别人兑现
>>>>>>finally Traceback (most recent call last): File "<stdin>", line 4, in <module> ZeroDivisionError: division by zero

输出结果说明,尽管with代码块中出现了异常,可是”>>>>>>finally“ 信息仍是被打印了,说明finally代码块被执行,即文件关闭操做被执行。可是结果中错误信息仍是被输出了,所以仍是建议用一个完成的try...except...finally语句对异常信息进行捕获和处理。

3. 最佳实践

为了不忘记或者为了不每次都要手动关闭文件,咱们可使用with语句(一种语法糖,语法糖语句一般是为了简化某些操做而设计的)。with语句会在其代码块执行完毕以后自动关闭文件。所以咱们能够这样来改写上面的程序:

with open('song.txt', 'r', encoding='utf-8') as f:
    print(f.read())
print(f.closed)

 

输出结果:

匆匆那年咱们 究竟说了几遍 再见以后再拖延
惋惜谁有没有 爱过不是一场 七情上面的雄辩
匆匆那年咱们 一时匆忙撂下 难以承受的诺言
只有等别人兑现
True

是否是变得简介多了,代码结构也比较清晰了。with以后打印的f.closed属性值为True,说明文件确实被关闭了。

思考:
with语句会帮咱们自动处理异常信息吗?

要回答这个问题就要提到“上下文管理器” 和 with语句的工做流程。

with语句不只仅能够用于文件操做,它其实是一个很通用的结构,容许使用所谓的上下文管理器(context manager)。上下文管理器是一种支持__enter__()和__exit__()这两个方法的对象。__enter__()方法不带任何参数,它在进入with语句块的时候被调用,该方法的返回值会被赋值给as关键字以后的变量。__exit__()方法带有3个参数:type(异常类型), value(异常信息), trace(异常栈),当with语句的代码块执行完毕或执行过程当中由于异常而被终止都会调用__exit__()方法。正常退出时该方法的3个参数都为None,异常退出时该方法的3个参数会被分别赋值。若是__exit__()方法返回值(真值测试结果)为True则表示异常已经被处理,命令执行结果中就不会抛出异常信息了;反之,若是__exit__()方法返回值(真值测试结果)为False,则表示异常没有被处理而且会向外抛出该异常。

如今咱们应该明白了,异常信息会不会被处理是由with后的语句返回对象的__exit__()方法决定的。文件能够被用做上下文管理器。它的__enter__方法返回文件对象自己,__exit__方法会关闭文件并返回None。咱们看下file类中关于这两个方法的实现:

def __enter__(self): # real signature unknown; restored from __doc__
    """ __enter__() -> self. """
    return self
    
def __exit__(self, *excinfo): # real signature unknown; restored from __doc__
    """ __exit__(*excinfo) -> None.  Closes the file. """
    pass

 

可见,file类的__exit__()方法的返回值为None,None的真值测试结果为False,所以用于文件读写的with语句代码块中的异常信息仍是会被抛出来,须要咱们本身去捕获并处理。

with open('song.txt', 'r', encoding='utf-8') as f:
    print(f.read())
    num = 10 / 0

  

输出结果:

匆匆那年咱们 究竟说了几遍 再见以后再拖延
惋惜谁有没有 爱过不是一场 七情上面的雄辩
匆匆那年咱们 一时匆忙撂下 难以承受的诺言
只有等别人兑现
Traceback (most recent call last): File "<stdin>", line 3, in <module> ZeroDivisionError: division by zero

注意: 上面所说的__exit__()方法返回值(真值测试结果)为True则表示异常已经被处理,指的是with代码块中出现的异常。它对于with关键字以后的代码中出现的异常是不起做用的,由于尚未进入上下文管理器就已经发生异常了。所以,不管如何,仍是建议在必要的时候在with语句外面套上一层try...except来捕获和处理异常。

有关“上下文管理器”这个强大且高级的特性的更多信息,请参看Python参考手册中的上下文管理器部分。或者能够在Python库参考中查看上下文管理器和contextlib部分。

5、Python文件读取相关方法


咱们知道,对文件的读取操做须要将文件中的数据加载到内存中,而上面所用到的read()方法会一次性把文件中全部的内容所有加载到内存中。这明显是不合理的,当遇到一个几个G的的文件时,必然会耗光机器的内存。这里咱们来介绍下Python中读取文件的相关方法:

方法 描述
read() 一次读取文件全部内容,返回一个str
read(size) 每次最多读取指定长度的内容,返回一个str;在Python2中size指定的是字节长度,在Python3中size指定的是字符长度
readlines() 一次读取文件全部内容,按行返回一个list
readline() 每次只读取一行内容

此外,还要两个与文件指针位置相关的方法

方法 描述
seek(n) 将文件指针移动到指定字节的位置
tell() 获取当前文件指针所在字节位置

下面来看下操做实例

1. 读取指定长度的内容

Python2
with open('song.txt', 'r') as f:
    print(f.read(12).decode('utf-8'))

 

输出结果:

匆匆那年

结果说明:Python2中read(size)方法的size参数指定的要读取的字节数,而song.txt文件是UTF-8编码的内容,一个汉字占3个字节,所以12个字节恰好是4个汉字。

Python3
with open('song.txt', 'r', encoding='utf-8') as f:
    print(f.read(12))

 

输出结果:

匆匆那年咱们 究竟说

结果说明:Python3中read(size)方法的size参数指定的要读取的字符数,这与文件的字符编码无关,就是返回12个字符。

2. 读取文件中的一行内容

Python2
with open('song.txt', 'r', encoding='utf-8') as f:
    print(f.readline())

 

Python3
with open('song.txt', 'r') as f:
    print(f.readline().decode('utf-8'))

 

输出结果都同样:

匆匆那年咱们 究竟说了几遍 再见以后再拖延

3. 遍历打印一个文件中的每一行

这里咱们只以Python3来进行实例操做,Python2仅仅是须要在读取到内容后进行手动解码而已,上面已经有示例。

方式一:先一次性读取全部行到内存,而后再遍历打印

with open('song.txt', 'r', encoding='utf-8') as f:
    for line in f.readlines():
        print(line)

 

输出结果:

匆匆那年咱们 究竟说了几遍 再见以后再拖延

惋惜谁有没有 爱过不是一场 七情上面的雄辩

匆匆那年咱们 一时匆忙撂下 难以承受的诺言

只有等别人兑现

这种方式的缺点与read()方法是同样的,都是会消耗大量的内存空间。

方式二:经过迭代器一行一行的读取并打印

with open('song.txt', 'r', encoding='utf-8', newline='') as f:
    for line in f:
        print(line)

 

输出结果:

匆匆那年咱们 究竟说了几遍 再见以后再拖延

惋惜谁有没有 爱过不是一场 七情上面的雄辩

匆匆那年咱们 一时匆忙撂下 难以承受的诺言

只有等别人兑现

另外,发现上面的输出结果中行与行之间多了一个空行。这是由于文件每一行的默认都有换行符,而print()方法也会输出换行,所以就多了一个空行。去掉空行也比较简单:能够用line.rstrip()去除字符串右边的换行符,也能够经过print(line, end='')避免print方法形成的换行。

file类的其余方法:

方法 描述
flush() 刷新缓冲区数据,将缓冲区中的数据马上写入文件
next() 返回文件下一行,这个方法也是file对象实例能够被当作迭代器使用的缘由
truncate([size]) 截取文件中指定字节数的内容,并覆盖保存到文件中,若是不指定size参数则文件将被清空; Python2无返回值,Python3返回新文件的内容字节数
write(str) 将字符串写入文件,没有返回值
writelines(sequence) 向文件写入一个字符串或一个字符串列表,若是字符串列表中的元素须要换行要本身加入换行符
fileno() 返回一个整型的文件描述符,能够用于一些底层IO操做上(如,os模块的read方法)
isatty() 判断文件是否被链接到一个虚拟终端,是则返回True,不然返回False

6、文件读写与字符编码


前面已经写过一篇介绍Python中字符编码的相关文件<<再谈Python中的字符串与字符编码>> 里面花了很大的篇幅介绍Python中字符串与字符编码的关系以及转换过程。其中谈到过两个指定的字符编码的地方,及其做用:

  • PyCharm等IDE开发工具指定的项目工程和文件的字符编码: 它的主要做用是告诉Pycharm等IDE开发工具保存文件时应该将字符转换为怎样的字节表示形式,以及打开并展现文件内容时应该以什么字符编码将字节码转换为人类可识别的字符。
  • Python源代码文件头部指定的字符编码,如*-* coding:utf-8 -*- 它的主要做用是告诉Python解释器当前python代码文件保存时所使用的字符编码,Python解释器在执行代码以前,须要先从磁盘读取该代码文件中的字节而后经过这里指定的字符编码将其解码为unicode字符。Python解释器执行Python代码的过程与IDE开发工具是没有什么关联性的。

那么这里为何又要谈起字符编码的问题呢?

或者换个问法,既然从上面已经指定了字符编码,为何对文件进行读写时还要指定字符编码呢?从前面的描述能够看出:上面两个地方指定的是Python代码文件的字符编码,是给Python解释器和Pycharm等程序软件用的;而被读写文件的字符编码与Python代码文件的字符编码没有必然联系,读写文件时指定的字符编码是给咱们写的程序软件用的。这是不一样的主体和过程,但愿我说明白了。

读写文件时怎样指定字符编码呢?

上面解释了读写文件为何要指定字符编码,这里要说下怎样指定字符编码(其实这里主要讨论是读取外部数据时的情形)。这个问题其实在上面的文件读取示例中已经使用过了,这里咱们再详细的说一下。

首先,再次看一下Python2和Python3中open函数的定义:

# Python2
open(name[, mode[, buffering]])
# Python3
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

 

能够看到,Python3的open函数中多了几个参数,其中包括一个encoding参数。是的,这个encoding就是用来指定被操做文件的字符编码的。

# 读操做
with open('song.txt', 'r', encoding='utf-8') as f:
    print(f.read())

# 写操做
with open('song.txt', 'w', encoding='utf-8') as f:
    print(f.write('你好'))

 

那么Python2中怎样指定呢?Python2中的对文件的read和write操做都是字节,也就说Python2中文件的read相关方法读取的是字节串(若是包含中文字符,会发现len()方法的结果不等于读取到的字符个数,而是字节数)。若是咱们要获得 正确的字符串,须要手动将读取到的结果decode(解码)为字符串;相反,要以特定的字符编码保存要写入的数据时,须要手动encode(编码)为字节串。这个encode()和decode()函数能够接收一个字符编码参数。Python3中read和write操做的都是字符串,其实是Python解释器帮咱们自动完成了写入时的encode(编码)和读取时的decode(解码)操做,所以咱们只须要在打开文件(open函数)时指定字符编码就能够了。

# 读操做
with open('song.txt', 'r') as f:
     print(f.read().decode('utf-8'))

# 写操做
with open('song2.txt', 'w') as f:
    # f.write(u'你好'.encode('utf-8'))
    # f.write('你好'.decode('utf-8').encode('utf-8'))
    f.write('你好')

 

文件读写时有没有默认编码呢?

Python3中open函数的encoding参数显然是能够不指定的,这时候就会用一个“默认字符编码”。
看下Python3中open函数文档对encoding参数的说明:

encoding is the name of the encoding used to decode or encode the file. This should only be used in text mode. The default encoding is platform dependent, but any encoding supported by Python can be passed. See the codecs module for the list of supported encodings.

也就是说,encoding参数的默认值是与平台有关的,好比Window上默认字符编码为GBK,Linux上默认字符编码为UTF-8。

而对于Python2来讲,在进行文件写操做时,字节会被直接保存;在进行文件读操做时,若是不手动进行来decode操做天然也就用不着默认字符编码了。可是这时候在不一样的字符终端打印的时候,会用当前平台的字符编码自动将字节解码为字符,此时可能会出现乱码。如song.txt文件时UTF-8编码的,在windows(字符编码为GBK)的命令行终端进行以下操做就会出现乱码:

>>> with open('song.txt', 'r') as f: ... print(f.read()) ... 鍖嗗寙閭e勾鎴戜滑 绌剁珶璇翠簡鍑犻亶 鍐嶈涔嬪悗鍐嶆嫋寤? 鍙儨璋佹湁娌℃湁 鐖辫繃涓嶆槸涓€鍦?涓冩儏涓婇潰鐨勯泟杈? 鍖嗗寙閭e勾鎴戜滑 涓€鏃跺寙蹇欐拏涓?闅句互鎵垮彈鐨勮瑷€ 鍙湁绛夊埆浜哄厬鐜

咱们应该尽量的获取被操做文件的字符编码,并明确指定encoding参数的值。

相关文章
相关标签/搜索