2015/9/9 Python基础(10):文件和输入输出
文件对象
文件对象不仅可以用来访问普通的磁盘文件,而且也可以访问其它任何类型抽象层面上的“文件”。一旦设置了合适的“钩子”,你就可以访问文件类型接口的其它对象,就好像访问的是普通文件一样。
文件对象的处理要以来很多内建函数,还有很多函数会返回文件对象或者是类文件对象。进行这种轴向处理的主要原因是许多输入/输出数据结构更趋向于使用通用的接口。这样就可以在程序行为和实现上保持一致性。文件只是连续的字节序列,数据传输经常会用到字节流,无论字节流是由单个字节还是大块数据组成。
文件内建函数[open()和file()]
内建函数open()(和file())提供了初始化输入/输出(I/O)操作的通用接口。open()成功打开文件后会返回一个文件对象,否则引发一个错误。当操作失败,Python会产生一个IOError异常。open()函数的基本语法是:
file_object = open(file_name, access_mode='r', buffering=-1)
file_name是包含要打开的文件名字的字符串,它可以是相对路径或者绝对路径。可选变量access_mode也是一个字符串,代表文件打开的模式。通常,文件使用模式'r','w',或是'a'模式来打开,分别代表读取,写入和追加。还有个'U'模式,代表通用换行符支持。
使用'r' 或 'U'模式打开的文件必须是已经存在的。使用'w'模式打开的文件若存在则首先清空,然后(重新)创建。以'a'模式打开的文件是为追加数据作准备的,所有写入的数据都将追加到文件的末尾,即使你seek到了其它地方。如果文件不存在,将被自动创建,类似以'w'模式打开文件,和C库函数fopen()使用的模式。
其它的fopen()支持的模式也可以再工作在Python的open()下。包括'+'表示可读写,'b'表示二进制模式访问。当然,对于所有POSIX兼容的Unix系统来说,'b'是可有可无的,因为它们把所有的文件当成二进制文件,包括文本文件。
以下是文件访问模式的详细列表,如果没有给定access_mode,它将自动采用默认值'r'。
文件模式 | 操作 |
r | 以读的方式打开 |
rU或Ua | 以读的方式 |
w | 以写方式打开(必要时清空) |
a | 以追加模式打开(从EOF开始,必要时创建新文件) |
r+ | 以读写模式打开 |
w+ | 以读写模式打开(参见 w ) |
a+ | 以读写模式打开(参见 a ) |
rb | 以二进制读模式打开 |
wb | 以二进制写模式打开(参见 w ) |
ab | 以二进制追加模式打开(参见 a ) |
rb+ | 以二进制读写模式打开 |
wb+ | 以二进制读写模式打开(参见 w ) |
ab+ | 以二进制读写模式打开(参见 a ) |
如:
fp = open('/etc/motd') # 以读方式打开
fp = open('test', 'w') # 以写方式打开
fp = open('data', 'r+') # 以读写方式打开
fp = open(r'c:\io.sys', 'rb') # 以二进制读模式打开
file()和open()函数具有相同的功能,可以任意替换。
一般来说,我们建议使用open()来读写文件,在您想说明您在处理文件对象时使用file(),例如 if instance(f, file)
通用换行符支持(UNS)
在os模块的一属性可以再不同平台下访问文件,不同平台用来表示行结束的符号是不同的,例如\n,\r,或者\r\n.我们希望用相同的方式处理所有的文件,所以我们使用了UNS。当使用'U'标志打开文件的时候,所有的行分割符通过Python的输入方式返回时都会被替换为换行符NEWLINE(\n).这个特性还支持包含不同类型行结束符的文件。文件对象的newlines属性会记录它曾看到的文件行结束符。
如果文件刚被打开,程序还没遇到行结束符,那么文件的newlines为None.在第一行被读取后,它被设置成第一行的结束符,文件的newlines会成为一个包含每种格式的元组。注意UNS只用于读取文本文件。没有对应的处理文件输出的方法。
在编译Python的时候,UNS默然打开,如果不需要这个特性,在运行configure脚本时,可以用——without—universal—newlines开关关闭它。
文件的内建方法
open()成功执行并返回一个文件对象后,所有的后续操作通过句柄执行,方法分为四类:输入,输出,文件内移动,杂项类操作。
输入
readline()方法读取打开文件的一行(读取下一个行结束符之前的所有字节)。然后整行,包括行结束符,作为字符串返回。和read()相同,他也有一个可选的size参数,默认为-1,代表读至行结束符。如果提供了改参数,在超过size个字节后返回不完整的行。
readlines()方法并不像其他两个输入方法一样返回一个字符串,它会读取所有(剩余的)行然后把他们作为一个字符串列表返回。它的可选参数sizhint代表返回的最大字节大小。如果它大于0,那么返回的所有应该有sizhint字节(可能稍微大于这个数字,因为要凑齐缓冲区大小)
Python里有一个对象来高效地迭代文件的行:xreadlines对象。调用file.xreadlines()等价于xreadlines.xreadlines(file). xreadlines()对象不是一次性读取所有的行,而是每次读取一块,所以在for循环时可以减少对内存的占用。不过随着迭代器和文件迭代的引入,没有必要再使用xreadlines()方法,因为iter(file)的效果也是一样,或者在for循环中,用for eachLine in file代替它。它来得容易,去的也快。
输出
write()内建方法功能与read()和readline()相反,它把含有文本数据或二进制数据块的字符串写入到文件中去。
和readlines()一样,writelines()方法是针对列表的操作,它接受一个字符串列表作为参数,将它们写入文件。行结束符并不会被自动加入,所以如果需要的话,你必须在调用writelines()前给每行结尾加上行结束符。
核心笔记:保留行分隔符
当使用输入方法如read()或者readlines()从文件中读取行时,Python并不会删除行结束符,这个操作被留给了程序员。例如这样的代码在Python程序中很常见:
f = open('myFile','r') data = [line.strip() for line in f.readlines()] f.close()
类似地,输出方法write() 或 writelines()也不会自动加入行结束符,应该在向文件写入数据前自己完成。
文件内移动
seek()方法(类似C的fseek())可以再文件中移动文件指针到不同的位置。offset字节代表相对于某个位置偏移量。位置的默认值为0,代表文件从开头算起,1代表从当前位置开始算起,2代表从文件末尾算起。
text()方法是对 seek()的补充,它告诉你当前文件指针在文件中的位置——从文件起始算起单位为字节。
文件的迭代
一行行访问文件很简单:
for eachLine in f: ...
在这个循环里,eachLine代表文件的一行(包括末尾的行结束符),可以用来做任何想做的事情。
在以前的Python里,因为没有引入迭代器,所以会有这样的写法:
for eachLine in f.readline(): ...
这是完成事情的老方法,现在可以不用readline()方法。
其它
close()通过关闭文件来结束对他的访问,Python的垃圾收集机制也会在文件对象的引用计数降为零的时候自动关闭文件,良好的编程习惯要求在重新赋另个文件对象前关闭这个文件,如果不显式关闭这个文件,那么可能丢失输出缓冲区的数据。
fileno()方法返回打开文件的描述符,是一个整数,可以用在如os模块( os.read())的一些底层操作。
flush()方法直接把内部缓冲区中的数据立刻写入文件,而不是被动地等待输出缓冲区被写入。isatty()是一个布尔内建函数,当文件是一个类tty设备时返回True,否则返回False。truncate()方法将文件截取到当前文件指针位置或者到给定 size,以字节为单位。
文件方法杂项
在《Python核心编程》第二章里有这样一个例子:
filename = raw_input('Enter file name:') f = open(filename, 'r') allLines = f.readlines() f.close() for eachline in allLines: print eachline, #加逗号是因为输出有换行符
这样一个方法是读完所有的行再向屏幕输出数据,如果文件太大,这样的读取方法并不好。所以用迭代器有这样的方法:
filename = raw_input('Enter file name:') f = open(filename, 'r') for eachline in f: print eachline, f.close()
因为print默认在内容末尾加换行符,语句后加一个逗号可以避免换行。而readline()和readlines()不对行内的字符处理,拷贝时已经拷贝了换行符,所以不能再让print自己换行,否则每行会有两个换行符。
对于换行符,因为不同操作系统使用了不同的换行符,会对程序的编写造成困扰,而Python考虑了这个问题
核心笔记:
行分隔符在不同操作系统里不同,POSIX(Unix或Mac OS X)上,换行符是\n ,而MacOS下用的是\r,DOS和Win32结合使用两者。另外路径分隔符在POSIX用"/"而DOS和Win用了"
\"
跨平台应用很麻烦,而Python的os模块设计者考虑到了这一点,os模块有五个很有用的属性
os 模块属性 | 描述 |
linesep | 用于在文件中分隔行的字符串 |
sep | 用于分隔文件路径名的字符串 |
pathsep | 用于分隔文件路径的字符串 |
curdir | 当前工作目录的字符串名称 |
pardir | 当前工作目录的父目录字符串名称 |
文件对象还有一个truncate()方法,接受一个可选的size作为参数,如果给定,那么文件将被截取到最多size字节处。如果没有size参数,那么默认截取到文件的当前位置。例如,刚打开一个文件,然后立即调用truncate()方法,那么你的文件(内容)实际上被删除,这时候你是其实从0字节开始截取(tell()会返回这个数值)
下面这个例子是输出到文件:
import os filename = raw_input('Enter file name: ') fobj = open(filename, 'w') while True: aLine = raw_input("Enter a line('.'to quit):") if aLine != ".": fobj.write('%s%s' % (aLine, os.linesep)) else: break fobj.close()
第二个例子是以可读可写模式:
>>> f = open(r'F:\tmp\x', 'w+') >>> f.tell() 0 >>> f.write('test line 1\n') # 加入一个长为12 的字符串 [0-11] >>> f.tell() 13L >>> f.write('test line 2\n') # 加入一个长为12 的字符串 [12-23] >>> f.tell() # 告诉我们当前的位置 26L >>> f.seek(-13, 1) # 向后移12 个字节 >>> f.tell() # 到了第二行的开头 13L >>> f.readline() 'test line 2\n' >>> f.seek(0, 0) # 回到最开始 >>> f.readline() 'test line 1\n' >>> f.tell() # 又回到了第二行 13L >>> f.readline() 'test line 2\n' >>> f.tell() # 又到了结尾 26L >>> f.close() # 关闭文件
文件对象的方法 | 操作 |
file.close() |
关闭文件 |
file.fileno() |
返回文件的描述符(file descriptor ,FD, 整数值) |
file.flush() |
刷新文件的内部缓冲区 |
file.isatty() | 判断 file 是否是一个类 tty 设备 |
file.nexta() |
返回文件的下一行(类似于file.readline() ), 或在没有其它行时引发 StopIteration 异常 |
file.read(size=-1) |
从文件读取 size 个字节, 当未给定 size 或给定负值的时候, 读取剩余的所有字节, 然后作为字符串返回 |
file.readintob(buf, size) |
从文件读取 size 个字节到 buf 缓冲器(已不支持) |
file.readline(size=-1) | 从文件中读取并返回一行(包括行结束符), 或返回最大 size个字符 |
file.readlines(sizhint=0) | 读取文件的所有行并作为一个列表返回(包含所有的行结束符); 如果给定 sizhint 且大于 0 , 那么将返回总和大约为sizhint 字节的行(大小由缓冲器容量的下一个值决定) |
file.xreadlinesc() |
用于迭代, 可以替换 readlines() 的一个更高效的方法 |
file.seek(off, whence=0) |
在文件中移动文件指针, 从 whence ( 0 代表文件其始, 1 代表当前位置, 2 代表文件末尾)偏移 off 字节 |
file.tell() |
返回当前在文件中的位置 |
file.truncate(size=file.tell()) |
截取文件到最大 size 字节, 默认为当前文件位置 |
file.write(str) |
向文件写入字符串 |
file.writelines(seq) | 向文件写入字符串序列 seq ; seq 应该是一个返回字符串的可迭代对象; 在 2.2 前, 它只是字符串的列表 |
文件的内建属性
文件除了方法外还有一些数据属性。
文件对象的属性 | 描述 |
file.closed | True表示文件已经被关闭,否则为False |
file.encoding | 文件使用的编码——当Unicode字符串写入数据时,它们将自动使用file.encoding 转换为字节字符串;若file.encoding为None时使用系统默认编码 |
file.mode | 文件打开时使用的访问方式 |
file.name | 文件名 |
file.newlines | 未读取到行分隔符时为None,只有一种行分隔符时为一个字符串,当文件有多种类型的行结束符时,则为包含所有当前所遇到的行结束符的列表。 |
file.softspace | 为0表示在输出一数据后,要加上一个空格符,1表示不加。这个属性一般只在内部使用 |
标准文件
一般说来,只要你的程序一执行,那么你就可以访问三个标准文件。它们分别是标准输入(一般是键盘),标准输出(到显示器的缓冲输出)和标准错误(到屏幕的非缓冲输出)。(这里所说的"缓冲"和"非缓冲"是指open()函数的第三个参数.)这些文件沿用的是C语言中的命名,分别为stdin,stdout和stderr。这三个文件在执行时都已经被预先打开了,只要知道文件句柄就可以随时访问这些文件。
Python中通过sys模块访问这些文件的句柄。导入sys模块以后,就可以使用sys.stdin, sys.stdout 和 sys.stderr访问。print 语句通常是输出到 sys.stdout ;内建的raw_input()则从 sys.stdin接受输入。
这些sys.*是文件,所以你必须自己处理好换行符,而print语句会自动在要输出的字符串上加换行符。
命令行参数
sys模块通过sys.argv属性提供了对命令行参数的访问。命令行参数是调用某个程序时除程序名以外的参数。
sys.argv是命令行参数的列表
len(sys.argv)是命令行参数的个数(也就是 argc)
命令行参数的意义是一个程序在执行完一些处理后,把结果作为流输出出来,这些输出的结果可以作为下一个程序输入,然后这样一次次延续下去,各个程序的输出一般是不保存的,这样可以节省大量的磁盘空间,各个程序的输出通常使用“管道”实现到下个程序输入的转换。
Python有两个模块来辅助处理命令行参数。其中一个是getopt模块,另一个是optparse。这里暂不详解。
文件系统
对文件系统的访问大多通过Python的os模块实现。该模块是Python访问操作系统功能的主要接口。os模块实际上只是真正加载的模块的前端,真正的那个“模块”明显要依赖于具体的操作系统。有POSIX,NT,Mac,OS2等等。不需要导入这些模块,只需要导入os模块,Python会为我们选择,这使得不需要考虑底层的工作。
除了对进程和进程运行环境进行管理外,os模块还负责处理大部分的文件系统操作,应用程序开发人员可能要经常用到这些,这些功能包括删除/重命名文件,遍历目录树,以及管理文件访问权限。
另一个模块 os.path 可以完成一些针对路径名的操作。它提供的函数可以完成管理和操作文件路径名中的各个部分,获取文件或各个部分,获取文件或子目录星系,文件路径查询等操作。
os 模块的文件/目录访问函数
函数 | 描述 |
文件处理 | |
mkfifo()/mknod() | 创建命名管道/创建文件系统节点 |
remove()/unlink() | Delete file 删除文件 |
rename()/renames() | 重命名文件 |
stat() | 返回文件信息 |
symlink() | 创建符号链接 |
utime() | 更新时间戳 |
tmpfile() | 创建并打开('w+b')一个新的临时文件 |
walk() | 生成一个目录树下的所有文件名 |
目录/文件夹 | |
chdir()/fchdir() | 改变当前工作目录/通过一个文件描述符改变当前工作目录 |
chroot() | 改变当前进程的根目录 |
listdir() | 列出指定目录的文件 |
gercwd()/getcwdu() | 返回当前工作目录/功能相同,但返回一个Unicode对象 |
mkdir()/makedirs() | 创建目录/创建多层目录 |
rmdir()/removedirs() | 删除目录/删除多层目录 |
访问/权限 | |
assess() | 检验权限模式 |
chmod() | 改变权限模式 |
umask() | 设置默认权限模式 |
文件描述符操作 | |
open() | 底层的操作系统 open (对于文件,使用标准内建 open()函数) |
read()/write() | 根据文件描述符读取/写入数据 |
dup()/dup2() | 复制文件描述符号/功能相同,但是复制到另一个文件描述符 |
设备号 | |
makedev() | 从 major 和 minor 设备号创建一个原始设备号 |
major()/minor() | 从原始设备号获得 major/minor 设备号 |
os.path 模块中的路径名访问函数
函数 | 描述 |
分隔 | |
basename() | 去掉目录路径, 返回文件名 |
dirname() | 去掉文件名, 返回目录路径 |
join() | 将分离的各部分组合成一个路径名 |
split() | 返回 (dirname(), basename()) 元组 |
splitdrive() | 返回 (drivename, pathname) 元组 |
splitext() | 返回 (filename, extension) 元组 |
信息 | |
getatime() | 返回最近访问时间 |
getctime() | 返回文件创建时间 |
getmtime() | 返回最近文件修改时间 |
getsize() | 返回文件大小(以字节为单位) |
查询 | |
exists() | 指定路径(文件或目录)是否存在 |
isabs() | 指定路径是否为绝对路径 |
isdir() | 指定路径是否存在且为一个目录 |
isfile() | 指定路径是否存在且为一个文件 |
islink() | 指定路径是否存在且为一个符号链接 |
ismount() | 指定路径是否存在且为一个挂载点 |
samefile() | 两个路径名是否指向同个文件 |
以下有一个使用示例:
#!/usr/bin/env python import os for tmpdir in(r'\tmp',r'D:\temp'): if os.path.isdir(tmpdir): break else: print 'no temp directory availiable' tmpdir = '' if tmpdir: os.chdir(tmpdir) cwd = os.getcwd() print '*** current temporary directory' print cwd print '*** creating example directory...' os.mkdir('example') os.chdir('example') cwd = os.getcwd() print '*** new working directory: ' print cwd print '*** original directory listing: ' print os.listdir(cwd) print '*** creating test file...' fobj = open('test','w') fobj.write('foo\n') fobj.write('bar\n') fobj.close() print '*** updated directory listing: ' print os.listdir(cwd) print "*** renameing 'test' to 'filetest.txt'" os.rename('test','filetest.txt') print '*** updated directory listing: ' print os.listdir(cwd) path = os.path.join(cwd,os.listdir(cwd)[0]) print '*** full file pathname' print path print '***(pathname,basename==)' print os.path.split(path) print '***(filename,extenion)==' print os.path.splitext(os.path.basename(path)) print '*** diaplaying file contents:' fobj = open(path) for eachLine in fobj: print eachLine, fobj.close() print '*** deleting test file' os.remove(path) print '*** updated directory listing:' print os.listdir(cwd) os.chdir(os.pardir) print '*** deleting test drectory' os.rmdir('example') print '***DONE'
核心模块: os (和 os.path)
从上面这些长篇讨论可以看出,os 和 os.path 模块提供了访问计算机文件系统的不同方法。我们在本章学习的只是文件访问方面,事实上 os 模块可以完成更多工作。我们可以通过它管理进程环境,甚至可以让一个Python程序直接与另外一个执行中的程序“对话”。
文件执行
无论你只是想简单地运行一个操作系统命令,调用一个二进制可执行文件,或者其它类型的脚本(可能是 shell 脚本,Perl, 或是 Tcl/Tk),都需要涉及到运行系统其它位置的其它文件。尽管不经常出现,但有时甚至会需要启动另外一个Python解释器。之后再会学到这部分的内容
永久存储模块
永久存储模块可以把用户的数据归档保存起来供以后使用,可以避免每次输入同样的信息。大部分永久性存储模块是用来储存字符串数据的,但是也有方法来归档 Python 对象。
核心模块: pickle 和 cPickle
可以使用 pickle 模块把 Python 对象直接保存到文件里,而不需要把它们转化成字符串,也不用底层的文件访问操作把它们写到一个二进制文件里。pickle 模块会创建一个 Python 语言专用的二进制格式,不需要考虑细节,它会干净利落地完成读写对象操作,唯一需要的只是一个合法的句柄。
pickle 模块中的两个主要函数是 dump() 和 load().
dump()函数接受一个文件句柄和一个数据对象作为参数,把数据对象以特定格式保存到给定文件里。当我们用load()函数从文件中取出已保存的对象时,pickle 知道如何恢复这些对象到它们本来的格式。
cPickle 是 pickle 的一个更快的 C 语言编译版本