第九章: 输入与输出

第九章: 输入与输出

章介绍Python I/O的基本知识,包括命令行选项、环境变量、文件I/O、Unicode, 以及如何使用Pickle模块序列化对象

9.1读取命令行选项

Python启动时,命令行选项放置在列表sys.argv中。第一个元素是程序的名称。后续项是在命令行上程序名称之后显示的选项。下面的程序给出了手动处理简单命令行参数的最小原型:

import sys

if len(sys.argv) != 3:

sys.stderr.write("Usage : python %s inputfile outputfile\n % sys.argv[0])

raise SystemExit(1)

inputfile = sys.argv[1]

outputfile = sys.argv [2]

 

在该程序中,sys .argv [0] 包含所执行脚本的名称。将一条错误消息写入到sys stderr并引发包含非零退出代码的SystemExit异常,这是命令行工具中报吿使用错误的标准做法。

尽管你可以手动处理简单脚本的命令选项,但对于更复杂的命令行处理,可以使用optparse模块。 下面给出了一个简单示例:

import optparse

p = optparse.OptionParser()

 

# 该选项接受一个参数

p.add_option("-o",action="store",dest="outfile")

p.add_option("--output", action="store", dest="outfile")

 

# 该选项设置一个布尔值标志

p.add_option("-d",action="store_true",dest="debug")

p. add_option ("--debug", action=" store_true", dest= "debug")

 

#设置选定选项的默认位

p.set_defaults(debug=False)

 

#解析命令行

opts, args = p.parse_args()

 

#检索选项设置

out file = opts.outfile

debugmode = opts.debug

 

在这个例子中添加了两类选项。第一个选项 -o  --output具有一个必需的参数。这种行为是通过在对p.add_option() 的调用中指定action=' store' 来选定的。第二个选项-d --debug仅用于设置布尔值标志。这是通过在p.add_option() 中指定action= storejsrue来实现的。 p.add_option() dest参数选择将在解析后存储参数值的属性名称。p.set_defaults() 方法设置一个或多个选项的默认值。该方法所使用的参数名称应该与为每个选项选择的目标名称匹配。如果没有选定默认值,则默认值将设为None

上面的程序能够识别以下所有命令行样式:

 

% python prog.py -o outfile -d infilel ... infileN

% python prog.py --output=outfile --debug infilel ... infileN

% python prog.py -h

% python prog.py --help

 

使用 p.parse_args() 方法执行解析。该方法返回一2元组 (opts, args), 其中opts是包含已

解析选项值的对象,args是命令行上未解析为选项的项目列表。选项值使用opts. dest检索,其中dest 是在添加选项时使用的目标名称例如,-o --output参数放置在opts.outfile中,而args是剩余参数的列表,如

[ 'infilel', ... , 'infileN' ]用户请承时,optparse模块自动提供 -h--help 选项来列出可用选项。糟糕的选项也会导致错误消息的出现。

这个例子仅仅展示了optparse模块的最简单用法。在第19章提供了一些更髙级选项的信息。

9.2环境变量

可以通过字典OS.environ访问环境变量,例如:

import os

path = os. environ ["PATH"]

user = os. environ ["USER"]

editor = os.environ["EDITOR"]

... etc ...

要修改环境变量,需要设置os.environ变量。

例如:

os.environ["FOO"] = "BAR"

修改OS. environ会同时影响到正在运行的程序和Python创建的子进程

9.3文件和文件对象

内置函数open (name [,mode [bbufsize]])用于打幵和创建文件对象,如下所示:

f = open("foo")  # 打开 "foo" 以供读取

f = open("foo", 'r') # 打开 "foo" 以供读取 (同上)

f = open ( "foo" , 'w' ) # 打开对象以进行写入

文件模式 'r' 表示读取, 'w' 表示写入,'a' 表示附加。这些文件模式假定采用文本模式,可以隐式地对换行字符 '\n' 执行转换。例如,在Windows上,写入字符 '\n' 实际上会输出2字符序列 '\r\n' (并且读取该文件时,'\r\n' 又会被转换为一个 '\n' 字符),如果正在处理二进制数据,可以将 'b' 附加到文件模式后面,如 'rb', 或, 'wb' 。这将禁用换行符转换,如果关注处理二进制数据的代码的可移植性, 则应该包含(在UNIX上,一个常见错误就是省略了 'b', 因为文本与二进制文件之间没有任何区别)。另外,由于模式之间的区别,也可能看到指定为 'rt', 'wt' 或 'at' 的文本模式,这样能更清楚地表明意图。  

  9.3文件和文件对象

    通过提供加号(+) 字符(如'r+'或'W+' ),可以打开文件进行直接更新。打开文件进行更新时,

可以同时执行输人和输出,只要所有输出操作在任何后续输入操作之前清除其数据即可。如果使用'W+' 模式打开文件,其长度首先会被截断为0。

  如果使用模式'U' 或' rU"打开文件,将会提供通用的换行符支持,以方便阅读。在由各种文件I/O函数返回的字符串中,该功能可将不同的换行符编码(如'\n'、'\r'和'\r\n') 转换为标准'\n'字符,从而简化跨平台工作。举例而言,如果在UNX系统上编写的脚本必须处理由Windows程序生成的文本文件,这项功能会很有用。

  可选的bufsize参数控制文件的缓冲行为,其中0表示没有缓冲,1表示进行了行缓冲,而负值要求采用系统默认设置。任何其他正值都表示将使用的近似缓冲区大小(以字节为单位)。

  Python 3向open() 函数添加了4个额外的参数,它们是open(name [,mode [,bufsize [,encoding [,errors I  newline [,closefd] ]] ]] ]) 。encoding是一个编码名称,如‘utf-8'

'ascii'。errors是处理编码错误的错误处理策略(如需了解更多信息,请参见本章后面有关Unicode的内容)。newline控制通用换行符模式的行为,可以设置为None、''、' \n'、' \r'或' \r\n'。如果设为None,以' \n'、' \r'或' \r\n'形式结尾的任何行都会被转换为‘\n'。如果设为' (空字符串),所有这些行结束形式都会被识别为换行符,但在输入文本中不会转换。如果newline拥有任何有效的值,该值将用于结束各行。closefd控制在调用close( )方法时,是否实际关闭底层文件描述符。默认情况下,该值设为True。

 

 

9-1列出了 file对象支持的方法。

9-1文件方法

f.read([n]) f. readline([n]) f.readlines([size])

f.write(s)

f. writelines(lines) f. close。 f.tellO

f. seek{offset [t whence])

f.isattyO

f.flushO

f.truncate([size])

f.filenoO

f.nextO

最多读取n个字节

读取单行输入的敁多字符。如果省略了n,该方法将读取整行

读取所有行并返回一个列表,5以6<可选的,用于指定在读取操作停止前在文 件上读取的近似字符数

写入字符串s

写入序列J3中的所有字符串 关闭文件 返回当前文件指针 査找新文件位置

如果f是一个交互式终端,则返回1 清除输出缓冲区 将文件截断为最多节 返回一个整数文件描述符

返回下一•行或引发Stoplteration.Python3中,对应的方法称为<)

readO方法以字符串的形式返回整个文件,除非使用可选的Jength参数指定了最大字符数。 readlineO方法返回下一行输人,包括最后的换行符。readlines()方法以字符串列表的形式返回所 有输入行。^adline()方法可以接受的最大行长度为n如果读取的一行比n个字符多,将返回前n 字符。行中的剩余数据将不会被丢弃,并在执行后续读取操作时返回。readlines 0方法接受一个Size 参数,指定在停止读取之前要读取的近似字符数。实际读取的字符数可能比这个数字大,具体取决于

www.TopSage.com

132 9章输入与输出

缓存了多少数据。

readlineUreadlinesO方法都能够识别各种平台,能正确处理不同的换行符表示形式(如 |\r\n9如果文件在通用换行符模式(•!;,或,rlT)中打开,换行符将被转换为\iV read()readlineO返回一个空字符串来表示文件结束EOF)。以下代码展示了如何检测EOF

条件:

while True:

line = f.readline() if not line:

break

读取文件中所有行的一种便捷方式是用for循环进行迭代。例如: #迭代文件中的所有行

for line in f:

#对某一行执行特定操作

注意,在Python2中,各种读取操作始终返回8位字符串,无论指定的文件模式是什么(是文本还 是二进制)。在Python 3中,如果在文本模式下打开文件,这些操作将返回Unicode字符串,如果在二 进制模式下打开文件,将返回字节字符串。

write ()方法将一个字符串写入到文件中,writelines ()方法将一个字符串列表写入到文件中。 write ()writelines ()不会将换行字符添加到输出中,所以生成的所有输出都应该已经包含所有必 要的格式。这些方法可以将原始字节字符串写入到文件中,但只有以二进制模式打开文件时才能实现。

在内部,每个文件对象都有一个文件指针,用于存储下次读取或写入操作所需的字节偏移位置。 tell ()方法以长整型返回文件指针的当前值。seek()方法根据给定的off set和油ence中的位置规则 随机访问文件的各个部分。如果whence0 (默认值),seek()假设offset为文件的开头丨如果whence 1,访问位置将移到当前位置,如果Wence2,偏移将移到文件末尾。seek()以整数形式返回文 件指针的新值。应该注意,文件指针与open()返回的文件对象相关联,而不是与文件本身相关联。可 以在同一个程序中(或在不同程序中)多次打开同一个文件。每个打开文件的实例都拥有自己的文件 指针,可以独立操作该指针。

filenoO方法返回文件的整数文件描述符,有时用在某些库模块中的低级1/0操作中。例如,在 UNIX系统上,fcnt 1模块使用文件描述符来提供低级文件控制操作。

文件对象还拥有一些只读数据属性,如表9-2所示。

9-2文件对象属性

f. closed

f.mode f.name f.softspace

f.newlines

f. encoding

布尔值,表示文件状态:如果文件已打开则为False,如果文件已关闭则为True

文件的I/O模式

如果使用open()创建文件,则为文件的名称。否则,它将是一个表示文件来源的字符串

布尔值,指示在使用print语句时,是否应该在一个值之前打印空格字符。模仿文件的类必须 提供该名称的一个可写属性,该属性初始化为0 (仅在Python 2中使用)

在通用换行符模式下打开一个文件时,该属性包含可在文件中实际找到的换行符表示。如果没 有遇到换行符,该值为None,将会看到一个包含、!!•、•或'An•的字符串,或者一个包含所 有不同换行符编码的元组

一个字符串,指示文件编码(如果存在)(例如atiri-l•或tf-8,)。如果没有使用任何编 码,该值为None

f

# EOF

www.TopSage.com

9.5 print 语句

133

9.4标准输入、输出和错误

解释器提供了3种标准文件对象,分别为标准输入、标准输出和标准错误,它们在sys模块中分别 sys.stdinsys.stdoutsys.stderr的形式提供。stdin是与提供给解释器的输入字符流相对应 的文件对象。stdont是一个文件对象,接收由print生成的输出。stderr是接收错误消息的文件。通 常,stdin被映射到用户的键盘,而stdoutstderr在屏幕上生成文本。

上一节描述的方法可用于执行用户的原始I/O。例如,以下代码写入标准输出并从标准输入中读 取一行输入:

import sys

sys.stdout.write(MEnter your name : n) name = sys.stdin.readline()

另外,内置函数raw_input fpro/npt;也可以从stdin读取一行文本,并可以打印一个提示符:

name

raw_inputEnter your

name :

raw_input ()读取的行不包含行末的换行符。这与直接从sys .stdin读取不同,在sys .stdin中, 换行符都包含在输入文本中0Python 3中,

input ()已被重命名为input ()

键盘中断(通常由Ctrl+C生成)会生成Keyboardlnterrupt异常,可使用异常处理程序捕获该异常

raw

如有必要,sys.stdoutsys.stdirsys.stderr的值可以替换为其他文件対象,在这种情况 下,print语句和输入函数将使用新值。如果需要还原sys.stdout的原始值,首先应该保存该值。在 解释器启动时,sys.stdout, sys.stdinsys.stderr的原始值可以分别在sys.__stdout_ sys.„ stdin__^Qsys. „st derr„ 中提供。

注意,在某些情况下,可以使用集成开发环境IDE)更改sys.stdinsys.stdoutsys.stderr 例如,当PythonIDLE下运行时,sys.stdin将被替换为一个对象,该对象的行为类似于文件,但它 却是开发环境中的一个对象。在这种情况下,某些低级方法(如readOseek())可能不可用。

9.5 print 语句

Python2有一个特殊的print语句,可根据sys •stdout中包含的文件生成输出。print接受一个逗 号分隔的对象列表,例如:

print "The values are*', x, y, z

对于每个对象,将调用str()函数来生成输出字符串。这些输出字符串然后会连接在一起,彼此 之间用一个空格分开,从而得到最终的输出字符串。输出通过一个换行符终止,除非为print语句提 供了结束逗号。在这种情况下,下一条print语句将在打印更多项目之前插入一个空格。该空格的输 出由用于输出的文件的soft space属性控制。

print "The values #打印相同的文本,使用两条print语句 print "The values are ", x, y, print z,

are

,x, y, z, w

»省略结束的换行符 #z的前面打印一个空格

要生成格式化输出,可以使用第4章中介绍的字符串格式运算符(%)或.format ()方法。下面给 —个例子:

print "The values print "The values

are

are

%d %7.5f %s?% (x,y,z) # 已格式化的I/O {0:d} {1:7.5f} {2format(x,y,z)

w

www.TopSage.com

134 9章榆入与输出

可以更改print语句的目标,方法是添加特殊的fiJe修饰符和一个逗号,其中file是一个允许 写入的文件对象,例如:

f = open{°output","w") print >>f, uhello world

f .closed

9.6 print ()函数

Python 3中最重要的更改之一是,print被转变为函数。在Python 2.6中,如果在使用的每个模块 中包含了语句from future__ import print_function,也可以将print用作函数。print ()函数 的工作方式与上一节中介绍的print语句非常相似。

要打印一系列以空格分隔的值,只需将这些值提供给print (},例如:

print("The values are", x

要禁止或更改行终止,可以使用endzendhsr关键字参数。例如:

#禁止换行符

要将输出重定向到一个文件,可以使用file=outfile关键字参数。例如:

print("The values are", x, y, z, end=''}

#重定向到文件对象f 要更改项目之间的分隔字符,可以使用sep=SepChr关键字参数。例如:

井在值之间添加逗号

print("The values are", x, y, z, file=f)

print("The values are", x, y, z, sep=1,')

9.7文本输出中的变量插入

生成输出时的一个常见问题是,生成其中包含丁嵌入式变量替换的大型文本片段。很多脚本语言 (如PerlPHP)都允许用$-变量替换形式(如$name将变量插入到字符串中。Python 无法直接实现这一功能,但可以通过将格式化1/0与三对带引号的字符串来模仿这种行为。例如,可以 编写一封简单的信函,在其中填写一个name一个item名称和一个amount,如下例所示:

#注意:||后紧跟的结束斜杠可以防止第一行显示为空行

form = ""\

□ear %(name)s,

Please send back my %(item)s or pay me $%(amount)0.2f.

Sincerely yours,

Joe Python User

H II II

print form % ( 'name': 'Mr. Bush', 'item': 'blender1, 'amount1: 50.00,

这段代码生成以下输出:

Dear Mr. Bush,

Please send back my blender or pay me $50.00.

Sincerely yours.

Joe Python User

www.TopSage.com

9.8生成输出 135

format (》方法是一种更加先进的替代方法,它可以使上面的代码更加简洁。例如:

form = """\

Dear {name},

Please send back my {item} or pay me {amount0.2f}.

Sincerely yours,

Joe Python User

print form.format(name='Mr. Bush1, item=■blender', amount=50.0)

对于某些格式类型,也可以使用Template字符串,如下所示:

import string

form = string.Template

Dear $name.

Please send back my $item or pay me $amount.

Sincerely yours.

Joe Python User

print form.substitute{{'name': 'Mr. Bush’

'item': 'blender',

• amount' : ,,%0.2fn % 50.0})

在这个例子中,字符串中的特殊$变量表示替换。form.substitute(>方法获取一个替换字典, 返回一个新字符串。尽管前面的方法很简单,但它们并不是最强大的文本生成方案。Web框架和其他 大型应用程序框架倾向于提供自己的模板字符串引擎,这些引擎支持嵌入式控制流、变量替换、文件 包含和其他髙级功能。

9.8生成输出

直接处理文件是程序员最熟悉的I/O模型。但是,生成器函数也可用于以一个数据片段序列的形 式输出1/0流。为此,只需使用yield语句,就像使用write ()print语句一样,例如:

def countdown(n): while n > 0:

yield "T-minus %d\nu % n n -= 1

yield "Kaboom!\n"

这种输出流生成方式非常灵活,因为输出流的生成与将输出流实际引导至期望目的地的代码是分开 的。例如,如果希望将上述输出发送到文件f,可以这样做:

count = countdown(5) f.writelines(count)

如果希望将输出重定向到套接字S可以这样做:

for chunk in count: s.sendall(chunk)

或者,如果只想将所有输出捕获到一个字符串中,可以这样做:

out

.join(count)

更高级的应用程序可以使用这种方法来实现自己的I/O缓冲。例如,一个生成器可以输出小文本片段, 但另一个函数可以将这些片段收集到大型缓冲区中,以实现较大的、更髙效的I/O操作:

www.TopSage.com

136 9章输入与输出

chunks =[]

buffered_size = 0

for chunk in count:

chunks.append(chunk) buffered_size += len(chunk) if buffered_size >= MAXBUFFERSIZE

outf.write(,B.join(chunks)) chunks.clear() buffered_size = 0

outf.write(0■.join(chunks)

对于将输出发送到文件或网络连接的程序,生成器方法还可以显著减少内存的使用,因为整个输 出流通常可以在较小的片段中生成和处理,而不需要首先收集到一个大型输出字符串或字符串列表 中。编写程序来与Python Web服务网关接口Web Services Gateway Interface,WSGI)交互时,可能 会用到这种输出方法,WSGI用于在Web框架的一些组件之间进行通信。

9.9 Unicode字符串处理

I/O处理相关的一个常见问题是,处理表示为Unicode的国际字符。如果有一个原始字节字符串s 其中包含已编码的Unicode字符串表示,那么可以使用s.decode([encoding I, errors]])方法将其 转换为合适的Unicode字符串。要将Unicode字符串適换为已编码的字节字符串,可以使用字符串方法 u. encode ([encoding [, errors}]).这两种转换运算符都需要使用一个特殊编码名称,指定如何 1;1^0也字符值与字节字符串中的一个8位字符序列上建立起映射关系。编码参数以字符串的形式指 定,是一百多种不同字符编码中的一种。以下值是最常用的:

ascii.

■latin-r'iso-8859-l •cpl252*

•utf-8'

■utf-16*

'utf-16-be'

nicode-escape.

• raw-unicode-escape'

7fi[ASC

ISO 8859-1 Latin-1

Windows 1252編码

8位变长编码

16位变长编码(可以为小尾或大尾) UTF-16,小尾编码 UTF-16,大尾编码

Unicode字面fiu ?string相同的格式 Unicode字面傲ur-string1•相同的格式

■utf-16-le'

}

默认编码在site模块中设置,可以使用sys.getdefaultencodingO査询。在很多情况下,默认 编码是 'ascii ',也就是说值在[0x000x7f ]范围内的ASCII字符可以直接映射到[U+0000U+007F] 范围内的Unicode字符。• utf-8 •也是一种非常常见的设置。与常见编码相关的技术细节将在后面介绍。

使用S.decode<)方法时,始终假定邊一个字节字符串。在Python2中,这表示s是一个标准字符 串,但在PythOn3中,s必须是一种特殊的bytes类型。类似地,t.encode(>的结果始终是一个字节序 列。如果注重可移植性,那么需要注意,这些方法在Python中稍微有点混乱。例如,Python 2字符串 同时拥有decode(}encode()方法,而在Python3中,字符串只有一•个encode()方法,bytes类型只 有一个decode<)方法。要简化Python 2中的代码,请确保仅对Unicode字符串使用encode()方法,仅

www.TopSage.com

9.10 Unicode I/O 137

对字节字符串使用decode ()方法。

转换字符串值时,如果遇到无法转换的字符,可能会引发UnicodeError异常。例如,如果尝试 将一个字符串编码为Ascii’并且它包含一个Unicode字符(如U+1F28),将会遇到编码错误,因为 该字符值太大,无法使用ASCII字符集表示。encode()decode{)方法的errors参数决定了如何处 理编码错误。该参数是一个包含以下值的字符串:

■strict'

'ignore'

■replace'

'backslashreplace'

'xmlcharrefreplace

遇到编码和解码错误时,引发UnicodeError异常

忽略无效字符

将无效字符替换为一个替换字符Unicode中的U+FFFD,标准字符串中的‘?|) 将无效字符替换为Pythor^符转义序列。例如,将字符U+1234替换为^1234 将无效字符替换为XML字符引用。例如,将字符U+1234替换为•ሴ广

默认错误处理选择是Strict’

要在网页上将国际字符嵌入到ASCII编码的文本中,’xmlcharrefreplace错误处理策略通常很 有用。例如,如果输出Unicode字符串1 Jalape\u00flo'并使用’xmlcharrefreplace1处理方法将它 编码为ASCII字符,浏览器始终能够将输出文本正确呈现为abpefio?,而不是乱码。

为了便于记忆,一定不要在表达式中混合使用字节字符串和未编码的字符串(例如使用+来连接 字符串)。Python 3禁止这样做,但Python 2将静默地支持这种操作,根据默认编码设置自动将字节字 符串提升为Unicode。这种行为通常会导致意外结果或无法理解的错误消息。因此,应该尽可能在程 序中分开已编码和未编码的字符数据。

9,10 Unicode I/O

处理Unicode字符串时,无法将原始Unicode数据直接写入文件。这是因为Unicode字符在内部表示 为多字节整数,而且将这些整数直接写入到输出流将导致与字节顺序相关的问题。例如,可能需要决 定是采用“小尾”格式将Unicode字符U+HHLL写为字节序列LLHH,还是采用“大尾”格式写为字节序 列hh LL。而且,处理Unicode的其他工具必须知道你所使用的编码形式。

因此,Unicode字符串的外部表示总是根据具体的编码规则来进行,该编码规则应该明确定义如 何将Unicode字符表示为字节序列。因此,要支持Unicode 1/0,需要将上一节中介绍的编码和解码概念 扩展到文件。内置的codecs模块包含一组函数,用于根据各种不同的数据编码方案,在字节数据与 Unicode字符串之间来回转换。

处理Unicode文件最直接方式是用codecs.open(filename [, mode [, encoding [, errors]]]

函数,如下所示:

codecs.open('foo.txt','r','utf-81,'strict') g = codecs.open('bar.txt'w'utf-8')

f

#读取 #写入

这两个函数创建了一个文件对象,用于读取或写入Unicode字符串。编码参数指定将在文件中读 取或写入数据时,用于转换数据的底层字符编码。errors^数决定如何处理错误,处理方式可以为上

一节中介绍的 1 strict''ignore11 replace', 'backslashreplace''xmlcharref replace

之一

如果已经拥有一个文件对象,可以使用codecs.EncodedFileffiJe, inputenc [, outputenc [, errors]])函数为该对象添加一个编码包装器,例如:

www.TopSage.com

138 9章输入与输出

f = open ("foo. txtn, "rb”

fenc = codecs.EncodedFile(f,'utf-8')

在这个例子中,根据inptitenc•中提供的编码对从文件中读取的数据进行解释。根据inputence 中提供的编码对写入文件的数据进行解释,并根据outputence中的编码来写入该数据。如果省略了 outputence,则默认编码与inpuCence相同。errors与前面介绍的含义相同。为现有文件添加 EncrodedFiJefe装器时,应确保该文件采用二进制模式。否则,换行符转换可能会违背编码规则。

处理Unicode文件时,数据编码通常嵌入到文件本身中。例如,XML解析器可能会査找字符串 •<?xml •..> •的前几个字节来决定文档编码。如果前4个值为3C 3F 78 6D ,则认为编

码是UTF-8。如果前4个值是00 3C 00 3F3C 00 3F 00则认为编码分别为UTF-16大尾或UTF-16 小尾。另外,、文档编码也可以出现在MIME头部,或者显示为其他文档元素的属性,例如:

<?xml ... encoding="ISO-8859-1" ... ?>

类似地,Unicode文件也能包含特殊的BOM (Byte-OrderMarker,字节顺序标记),指示字节编码 的特性。Unicofc字符u+feff就是为该用途而保留的,通常,BOM作为文件中的第一个字符写人,程 序读取该字符并査看字节的排列顺序,从而确定编码(例如•的编码为UTF-16-LE, '\xfe\xff'的编码为UIF-16-BE), i确定了编码,就会丢弃B0M字符,然后处理文件的剩余部分。 然而,这种额外的BOM处理不会在后台进行。通常,如果应用程序使用到这一功能,你必须给予注意,

从文档读取编码时,可以使用以下代码将输人文件转换为已编码的数据流:

f = openCsomefile", "rb"

»确定文件的編码

#为文件添加一个合适的编码包装器.

#假设BOM (如果存在)已被前面的语句丢弃. fenc = codecs.EncodedFile(f,encoding) data = fenc.read()

9.10.1 Unicode 数据编码

9-3列出了 codecs模块中一些最常用的编码器。

9-3 codecs模块中的编码器

编铒器

■latin-1'. 'iso-8859-l ■cp437'

>cpl252 '

'utf-8'

■ucf-16

'uLL-16-le'

'ur.f-16-be'

''Unicode-escape 'raw-unicode-escape'

ASCII编码

Ladn-lSISO-8859-l 编码 CP437编码 CP1252编碍 S位变长编码 16位变长编码

UTF-16,采用显式小尾编码 UTF-16,采用显式大尾编码 uustrinsr■格式相同

ur" string"格式相同

'fiscii'

www.TopSage.com

9.10 Unicode I/O 139

以下各节分别详细介绍毎个编码器。

1 ascii •编码

1ascii •编码中,字符值被限定在[0x00, 0x7f ][U+0000, U+007F]范围之内。此范围外的 任何字符都是无效的。

'ioo-8859-1', .latin-1.编码

字符可以是[0x00, 0x££]ro+0000, U+OOFF]范围内的任何8位值。[0x00, 0x7f]范围内的值 ASCII字符集中的字符对应。[0x80, Oxff]范围内的值与ISO-8859-1或扩展的ASCII字符集中的字符 对应,值在[0x00, Oxff]范围外的任何字符都会导致错误。

‘1^437 •编码

该编码类似于430-8859-1 •,但它是Python在作为Windows上的控制台应用程序运行时的默认编 码。[x80, Oxff]范围内的某些字符与遗留DOS应用程序中用于呈现菜单、窗口和帧的特殊符号对应。

’CP1252’编稱

这种编码与Windows上使用的iso-8859-V非常相似,但是,该编码定义[0x80, Qx9£]范围内 未在iso-8859-l'中定义且在Unicode中具有不同码点的字符。

.utf-8'编码

UTF-8是一种变长编码,可以表示所有Unicode字符,使用单个字节表示0-127范围内的ASCII

符。所有其他字符使用域3字节组成的多字节序列表示,这些字节的编码如下所示:

Unicode 字符

字节0

宇节1

字节2

U+DOOO - U+007F U+Q07F - U+07FF U+0800 - U+FFFF

Onnnnnnn

HDnnnnn

lllOnnnn

Wnnnnnn

lOnnnnnn

lOnnnnnn

对干2字节序列,第一个字节始终以位序列110开头,对于3字节序列,第一个字节始终以位序列 1110开头,多字节序列中所有后续数据字节都以位序列10开头。

概括来讲,UTF-8格式允许最多6字节的多字节序列sPython中,4字节OTF-8序列用于编码一个 称为代理对的Unicode字符对。字符对中两个字符的值都在[U+D800, u+dfff]范围内,并被组合起来 编码成一个20位的字符值s代理项编码如下所示:4字节序列llllOnnn lOrmnnnn lOnnnranm lOnuranmm 被编码为U+D800 + NU+DCOO + fB寸,其中N是编码为4字节UTF-8序列中的20位字符的髙10位,M 10位。5和6字节UTF>8序列(分别由最前面的位序列111U01U1110表示)用于编码长度达32位的字 符值。Python不支持这些值,如果在已编码的数据流中出现了这些值,将导致UnicodeError异常,

UTF-8编码具有很多有用的特性,早期的软件都可使用它_首先,标准ASCII字符采用标准编码 来表示。这意昧着UTF-8编码的ASCII字符串与传统ASCII字符串是没有差别的。其次,UTF-8不会为 多字节字符序列引人嵌人式NULL字节.因此,基于C库的现有软件和要求8位字符串以NULL终结的 程序将采用UTF-8字符串。最后,UTF-8编码会保持字符串的字母顺序。也就是说,如果ab^Unicode 字符串,并且Kb,那么当3和啪转换为UTP-埘,仍然成立,因此,分类算法和其他为8位字符 串编写的排序算法同样适用于UTF-8。

6. 'utf-16'. 'utf-16-ba■和,utf-16-le,编码

OTF-16是一种变长16位编码,采用这种编码方式时,Unicode字符被写为16位值。除非指定了字 节顺序,否则将假定采用大尾编码。另外,字节顺序标记U+FEFF可用于显式指定UTF-16数据流中的 字卞顺序。在大尾编码中,U+FEFF是零宽非断行空格nonbreaking space)Unicode字符,而保留值

www.TopSage.com

140 9章输入与输出

U+FFFE是一个无效的Unicode字符,因此,编码器可以使用字节序列FE FFFF FE来确定数据流的字 节顺序。读取Unicode数据时,Python会从最终的Unicode字符串中刪除字节顺序标记。

.utf-16-be'編码明确选择了UTF-16大尾编码。.utf-16-le.明确选择了UTF-16小尾编码,

尽管OTF-16的一些扩展可以支持比16位更大的字符值,但目前Python不支持所有这些扩展。

7. .unicode-eacape■和■raw-unicode-eBcape,编妈

这两种编码方法用于将Unicode字符串转换为在Python Unicode字符串字面量和Unicode原始字符 串字面量中使用的格式,例如:

u'\ul4afi\u0345\u2a34' t - s.encode('unicode-escape') #t = '\ul4a8\u0345\u2a34'

s

9.10.2 Unicode 字符特性

除了执行I/O,使用Unicode的程序可能还需要测试Unicode字符的各种特性,例如大小写形式、数 字和空格。unirodoiato模块能够访问一个字符特性数据库。常见的字符特性可使用' unicodedata.category (c)固数获得。例如,unicodedata.category (u"An}返回'Lu1,指示该字 符是一个大写字符。

Unicode字符串的另一个棘手问题是,同一个Unicode字符串可以有多种表示。例如,字符U+00F1 (n)可以完全组合为单一字符U+CCF1,或者分解为多字符序列U+COSeU+l^O3 (n,-)。如果无法一 致地处理Unicode字符申,可以使用unicodedata.normalize (>函数来确保一致的字符表示。例如, unicodedata.normalize('NFC', s)将确保s中的所有字符都完全组合在一起,而不会表示为一个宇 符组合序列。

关于Unicode字符数据库和unieodedata模块的更多细节可以在第16章找到。

9.11对象持久性与pickle模块

最后,常常需要将对象内容保存到一个文件中或从中进行还原> 执行该任务的一种方法是編写两 个函数,以一种特殊格式在文件中读取和写入数据。另一种方法是用piclcleshelve模块。

pickle模块将对象序列化为一个字节流,这个字节流可以写入到文件并在以后进行还原。Pickle 的接口非常简单,只包含(>和load()操作。例如,以下代码将一个对象写人一个文件:

import pickle

obj = someobject[) f = open(filename,'wb') pickle.dump(obj, f) f.closet)

#将对象保存到£上

要还原对象,可以使用以下代码:

import pickle

f = openffilename,*rb') obj = pickle.Xoad(f) f.closet)

»还原对象

可以依次发出一系列dump (>操作来保存对象序列。要还原这些对象,只需使用一•类似的load()

操作序列

shelve模块类似于pickle,但它将对象保存在一个类似字典的数据库中:

import shelve

www.TopSage.com

9.11对象持久性与pi

obj = Someobject()

shelve.open ("filename")

db[ •key') = obj

dh

I打开一个shelve #将对在shelve

#检索对象 # 关闭shelve

尽管由shelve创建的对象类似于一个字典,但它具有一些限制。首先,键必须是字符串。其次, shelve中存储的值必须与pickle兼容。大部分Python对象都能如此工作,但具有特殊用途的对象(如 文件和网络连接)将保持一种内部状态,这种状态无法通过这种方式保存和还原。

Pickle使用的数据格式是Pythcm所专用的。但是,随着Python版本的升级,该格式也被多次改进。 可以使用pickledump(oiu\ file, protocol)操作的一个可选协议参数来选择协议0

默认情况下使用协议0。这是最古老的Pickle数据格式,它将对象存储为几乎所有Python版本都 能够理解的格式,但是,这种格式还与Python很多更先进的用户定义类特性(如slot)不兼容。协议1 和协议2使用一种更髙效的二进制数据表示。要使用这些替代协议,可以执行如下操作:

obj = dbl'key'] db.closed

import pickle

obj = someobject(1

f = open(filename,'wb']

pickle. dujnp {obj, f, 2)

pickle.dump(obj,f,pickle.HIGHSST„PROTOCOL)

£. closed

使用leadI)还原对象时,不必指定协议.底层协议已被编码到文件中。

类似地,可以打开一个shelve来使用替代的piclcle协议保存Python对象,如下所示 import shelve

shelve.open{filename,protocol=2)

协议

db

用户定义的对象通常无需执行任何额外的操作来处理pidde?She:lVe,但是,可以使用特殊方 &__getstate_()甠_setstate_<>来协助组合pickle)过程,如果已定义了_getstate_(| 法,那么将调用它来创建一个表示对象状态的值、返回的值通常应该是一个字符串, 元组、列表或字典。(>方法在拆解unpickle)间接收此值,并且应该从该值还原对 象状态。下面的例子展示了如何使用这些方法来处理一个包含底层网络连接的对象。尽管无法组合实 际连接,但对象保存的信息已足够在拆解连接之后重建连接:

=

addr

import socket class Client(object):

def __init__{self,addrl: self, server_addr

self.sock = socket.socket(socket.af_inet,socket.SOCK_STREAM) self.sock.connect(addr) def getstate_(self):

return selE.server_addr def _setstate„(self.value): self.server_addr = value

self.sock = socket.socket(socket.AF_INBT,socket.sock_stream) self.sock.connect(self.server_addr)

由于pickle使用的数据格式是Python专用的,所以在使用不同编程语言编写的应用程序之间交换 数据时,不应使用该功能。而且,出于安全的考虑,程序不应该处理来自不可信来源的已组合的数据, (在拆解期间,经验丰富的攻击者可以操作组合数据格式来执行系统命令.

pickleshelve模块还拥有很多自定义功能和髙级使用选项。更多细节请参见第13.

www.TopSage.com

 

I

 

猜你喜欢

转载自www.cnblogs.com/amou/p/9029536.html