第七章 输入与输出 ——python导引编译之八

第七章 输入与输出 ——python导引编译之八

标题7.输入与输出 Input and Output

有一些表达一个程序的输出方式;数据可以打印在一个人皆可读的形式之中,或者,为未来使用写到一个文件上。这一章,将讨论这样一些可能性。

7.1. 对输出格式化有特殊爱好的人Fancier Output Formatting
到目前为止,我们面对两种书写价值的方式:表达式陈述和打印函数(第三种方式被用作对象的书写函数方法(write());标准的输出文件可以参见函数sys.stdout。有关这个函数的更多信息,请参看图书馆参考。)

如果你把你的输出格式与简单打印的分割空间值进行比较,你会常常想去加大对你输出格式的控制。有几种格式输出的方式。
第一种,为了使用格式化字符串文字;字符串的开始要带有一个f或者F,在打开的引入导述的标志符号或者三引述符号标志之前。在这个字符串之内,你可以花括号{ }之中,写出一个python表达式,文字可以涉及到变元或者文字值。

>>> year = 2020
>>> event = 'Referendum'
>>> f'Results of the {year} {event}'
>>> year = 2020
>>> event = 'Referendum'
>>> f'Results of the {year} {event}'
'Results of the 2020 Referendum'

第二种,字符串的str.format()方法要求更多的手动操作。你仍然使用花括号{}去标记一个变元将被替换的位置,同时能够提供详细的格式化目录,但是,你也需要提供被格式化的信息。

>>> yes_votes = 42_572_654
>>> no_votes = 43_132_495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> '{:-9} YES votes {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes 49.67%'

第三种,最后,你可以用字符串切片和相关的操作去做所有那些掌握在你手中的字符串,以创建你可以设想的任意安排。这些字符串类型有一些有用的操作方法,去为字符串材料给定表格行列的宽度。
当你不需要设想输出,但却需要为调试的目的显示快速那些变元的时候,你可以用函数repr()或者函数str(),改变字符串的任意值。
函数str()意思是返回这样一些值的陈述,这些值对于一般人是相当可读的,另一个函数repr()则意味着生成一些陈述,这些陈述可以被解释器读取(或者,如果出现并非等价的句法时,将强制地产生一个句法错误SyntaxError)。对于这样一类对象,它们没有一个对于人群消费的特别陈述,函数str()将返回同样的如同repr()的值。许多值,例如像列表,字典一类的数字或者结构的值,在上述每一种函数中使用同样的陈述。特别是字符串,有两种完全不同的陈述。
例如:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is' + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is, and y is 40000...
>>> s = 'The value of x is '+ repr(x) + ' , and y is ' + repr(y) + '...'
>>> prints(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'prints' is not defined
>>> print(s)
The value of x is 32.5 , and y is 40000...
>>>
>>> hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> repr((x, y,('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

字符串模块含有一个模板Template类,这个模板类给出另一种方法,去把值替换放进字符串,它使用类似$x这样的占位符,并用来自字典的值取代,但是这样的替换,对于格式的控制力度就会减少。

标题7.1.1. 格式字符串的文字Formatted String Literals

格式化字符串的文字(也简称为f-字符串)包括以下两个部分,那就是你首先有个前缀f或者F,然后跟随一个花括号,在花括号内的文字就是一个python表达式{python表达式},或者说该表达式的值。
一个可选的格式化区分符(specifier)可以跟随这个表达式。这允许更大的对于那些被格式化的值的控制。以下圆周率pi在小数点之后的三位数字的实例:

>>> import math
>>> print(f'The value of pi is approximately{math.pi..3f}.')
  File "<fstring>", line 1
    (math.pi..3f)
             ^
SyntaxError: invalid syntax
>>> print(f'The value of pi is approximately{math.pi:.3f}.')
The value of pi is approximately3.142.

在这个’:'之后通过的一个整数将获得这样的一个结果,产生一个字符宽度的最小数范围,这对于建构行列的升降是有帮助的。

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10}==> {phone:10d)')
...
  File "<stdin>", line 2
SyntaxError: f-string: expecting '}'
>>>     print(f'{:10} ==> {phone:10d}')
  File "<stdin>", line 1
    print(f'{:10} ==> {phone:10d}')
    ^
IndentationError: unexpected indent
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

在表达式格式化之前,还有一些修改方式也可以用来转换表达式的值。‘la’应用函数ascii(),'ls’应用函数str(),还有’lr’应用repr():

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals/r}.')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'r' is not defined
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

对于这些格式化项目的参考文献,可参看格式化说明迷你语言的参考指南

标题7.1.2. 字符串格式化方法The String format() Method

字符串格式化str.format()基本用法是像这样的:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

在这个打印print后的括弧和文字覆盖的地方,这个空间称为格式化域,但这些括弧和符号都用走进str.format()函数的对象所取代。括弧中的数字可以用作提及走进函数str.format()的对象位置。

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

如果关键字参数用在str.format()方法上,它们的值就通过参数名称的使用而被提及。

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

位置和关键字参数可以任意地结合:

>>> print('The story of {o}, {1}, and {other}.'.format('Bill', Manfred',
  File "<stdin>", line 1
    print('The story of {o}, {1}, and {other}.'.format('Bill', Manfred',
                                                                       ^
SyntaxError: EOL while scanning string literal
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred', other='Georg'))
The story of Bill, Manfred, and Georg.

解释器中的代码打入注意不要回车,直接接过去就行。把Georg‘))打完后再回车,否则有语形错误。
如果你有一个很长的格式代码,你又不想分开他,如果你可以用名称而不是用位置来提及这个变元,那就做得很漂亮。这是可以做到的,通过那个字典,并且用方括号"[]"登录那个关键字。

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack': {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
  File "<stdin>", line 1
    print('Jack': {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
                ^
SyntaxError: invalid syntax
>>> table = {'Sjoerd': 4172, 'Jack':4098, 'Dcab':8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd:{0[Sjoerd]:d};' 'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd:4172;Dcab: 8637678

同样是注意长代码一气写完,不要回车。
还可以用带两个星号**的符号作为关键字参数,做到这一点。

table = {‘Sjoerd’: 4127, ‘Jack’: 4098, ‘Dcab’: 8637678}
print(‘Jack’: {Jack:d}; Sjoerd:{Sjoerd:d}; Dcab: {Dcab:d}’.format(**table))
File “”, line 1
print(‘Jack’: {Jack:d}; Sjoerd:{Sjoerd:d}; Dcab: {Dcab:d}’.format(**table))
^
SyntaxError: invalid syntax

table = {‘Sjoerd’: 4127, ‘Jack’: 4098, ‘Dcab’: 8637678}
print(‘Jack:{Jack:d}; {Sjoerd:d}; Dcab: {Dcab:d}’.format(**table))
Jack:4098; 4127; Dcab: 8637678

这个代码也容易打错,注意引号位置。
这个函数在组合内置函数var()中非常有用,他能返回一个字典,这个字典含有所有的局域变元。
以下是一个实例,它产生一个排列整齐的行列集成,给出项目的整数,平方数和立方数。

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

使用函数str.format()的完整观察,请看格式化字符串句法学Format String Syntax。

标题7.1.3.手动字符串格式化 Manual String Formatting

这里有一个同样的平方表和立方表,用手动格式化构成:

>>> for x in range(1. 11):
  File "<stdin>", line 1
    for x in range(1. 11):
                      ^
SyntaxError: invalid syntax
>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     #注意end在前述行中的使用
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(注意,每一个行列之间的一个空格是用函数print()起作用的方式加进的:它总是增加参数之间的空格。)
字符串对象右对齐的函数str.rjust方法,是通过给字符串左边空间一个给定宽度的范围内加进的。类似的还有左对齐函数str.ljust(),中间对齐的函数str.center()。这些方法并不会写进任何东西,它们只是返回一个新字符串。如果输入的字符串太长,他们不会缩短,而是不作改变的返回它;这会搞乱你的行列设计,但是通常会比可选的方案更好,这会依赖相关值。(如果你真想截断一些,就总是可以添加一个切片操作,如同这个指令x.ljust(n)[:n].)中的那样。
还有一种方法,函数str.zfill(),它在左边用数字0塞进一个数字字符串。这个函数理解加符号和减符号:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

标题7.1.4.旧字符串格式化 Old string formatting

那个%的运算子(模块)也可以用作字符串的格式化。给定‘字符串’值,在字符串中使用%的例子就被值的零元素或者更多的元素所替代。这个运算通常被看作是字符串的改写。例如:

>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

更多信息可在printf-style String Formatting部分中看到。

标题7.2. 读取和编写文件Reading and Writing Files

函数open()返回到一个文件对象,更为普通地是使用两个参数的函数:open(filename, mode)。

>>> f = open('workfile', 'w')

这里的第一个参数是含有那个文件的字符串。这里的第二个参数是另外一个字符串,该字符串含有少数描述其路径的字符,而这个路径则是该文件将被使用的路径。参数mode可以是‘r’,当文件仅被读取的时候;参数mode为‘w’的时候,则表示文件被编写(带有同样名称而存留的文件会被删除);带有‘a’,则打开那个添加过的文件;任意已经写在文件上的数据将自动地添加到文档的末尾。’r+‘打开既读取又编写的文件。mode参数是可选的;如果参数省略,则假定为是’r‘读取。
正规的方式,文件是在文本模式中打开。这意味着,你读取和编写的字符串是来自这个文件并且写到这个文件,这个文件在一个特定的编码中进行编码。如果编码未被指定,缺省指定的是所依赖的平台(参看open())。添加到mode模式中的’b‘表示打开的文件是用二进制模式构造的文件:现在数据在比特形式下进行读取和编写。这个模式应该用到所有不含有文本的文件。
在文本模式中,当仅读取时的缺省值,转换指定平台的末端行(在Unix中是\n,在Windows中是\r\n)使用\n指令。当在文本模式中编写时,缺省值是转换\n的出现返回到指定平台行的末端。对于文件数据的场景背后的这种修改,对于文本文件很是合适,但是会搅乱二进制数据,像jpeg或者EXE之类的文件。当读取或者编写这样一类文件时,要非常小心地使用二进制模式。
一个较好的读写实践是在处理这类文件对象时,连带使用关键字with。这样做的好处是,全部任务完成后,文件会合宜地关闭,即使在某个点上有异常发生。而且这样使用关键字with,比起使用等价的函数模块try-finialy,文本也会更为简约。

>>> with open('workfile') as f:
...     read_data = f.read()
... # 我们可以检查该文件已经自动关闭
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'workfile'
>>> f.closed

这个代码不知为什么回不到>>>指示符,以至那个f.closed执行不了。且留下这个疑惑,继续前行。
无意中解决了这个问题,只要把那个f给个定义就行。

>>> f = open('workfile', 'w')
>>> with open('workfile') as f:
...     read_data = f.read()
...
>>> f.closed
True

如果你不是在使用关键字with,那么你就该调用f.close()去关闭那个文件,并且立刻释放被这个文件使用的任何系统资源。如果你并未明确地关闭文件,python的垃圾箱将会在最后毁掉这个对象,关掉你的这个文件,但是 文件也许会打开留置一会儿。另一个风险是,不同的python执行将会做这种在不同时间进行清理的事。
在一个文件对象关闭之后,或者是用一个with陈述,或者是调用f.close()函数,意在使用这个会自动失效的文件对象。

>>> f = open('workfile', 'w')
>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

注意:得先定义f,不然这两个函数中的第二个写不下来。

标题7.2.1. 文件对象方法Methods of File Objects

在这一节中还有一些其它的例子将假定:一个文件对象,它调用了已经被创建的f。
为了读取文件内容,调用f.read(size),它读取数据的数量并且把这些数量返回为字符串(在文本模式中)或者二进制对象(在二进制模式中)。那个size是一个可选数字参数。当size省掉或者被否定,该文件的全部内容将被读取和返回;如果该文件比你的机器内存打两倍,那可是你自己的问题。否则,size的字符(在文本模式中)或者size的比特(二进制模式中)按最大量读取和返回。如果文件的末端到了的话,f.read()函数将返回一个空字符串(‘ ’)。

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

函数f.readline()读取一个单一的行;一个新行字符(\n)在字符串的尾端留存,如果文件不在新行尾端文件最后一行将被略去。这使得返回值十分明确;如果f.readline()返回一个空字符串,则这个文件的尾端已经到达,空白行用‘\n’表示,仅含有一个新行的字符串。

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

要从一个文件中读取数行,你可以对文件对象使用循环for。这是存储性高效,快速和导致简单代码的方式:

>>> f = 'This is the entire file.\n'
>>> for line in f:
...     print(line, end='')
...
This is the entire file.
>>> f = 'This is the first line of the file.\n'
>>> for line in f:
...     print(line, end=' ')
...
T h i s   i s   t h e   f i r s t   l i n e   o f   t h e   f i l e .

如果你想读取在一个列表中一个文件所有的行,你也可以使用函数list(f)或者f.readlines()。
函数f.write(string)写下这个文件中的字符串的内容,返回所写文字的数量。

>>> f.write('This is a test\n')
15

执行不了,暂且复制于此。
需要转换的另一些对象类型-或者是给定字符串(文本模式)或者给定比特(二进制模式),在编写它们之前。

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

执行不了,暂且复制
函数f.tell()返回一个给定的整数,该整数在二进制模式中表述为比特数字在文件中的当前位置,而在文本模式中则表述为某个含糊数字在文件中的当前位置。
为改变该文件对象的位置,使用函数f.seek(offset, whence)。文件位置则被从增加offset到一个参考点而得到计算;那个参考点又通过参数whence来进行挑选。一个参数whence的0值标志了文件的开始,1值使用当前的文件位置,2值作为参考点的文件的尾端。参数whence可以省略或者缺省,缺省值为0,它作为参考点的那个文件的开始。

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

执行不了暂且搁置,复制。
在文本文件中,(那些打开的文本文件,在模式字符串中没有a b)仅仅找寻被允许的相关文件的开头(例外被寻找的文件末尾,可以用函数seek(0, 1))并且,唯一有效的offset值是那些从函数f.tell()返回的值,或者是0值。任何其它offset值都会出现未可定义的行为。
文件对象有某些其它的方法,例如isatty()和truncate(),它们很少被运用;文件对象更多的参考资料可参看图书馆参考。

标题7.2.2. 用json来保存结构了的数据Saving structured data with json

字符串很容易从文件中读取,也很容易在文件中编写。数字则要下点功夫才能读取或者编写,因为函数read()方法仅仅返回字符串,数字字符必须通过一个类似int()的函数才能得到,这个int(),接受一个‘123’的字符串,返回其数字值123. 当你想保存更复杂的数据类型,例如嵌套列表和字典时,用手动分析和连接这些文件就会很复杂。
不是让用户不断地编写和调试编码去保存复杂类型数据给文件。python 允许你去使用流行的数据交换格式化的指令。名为JSON(JavaScriptObjectNotation)。标准模块称为json,这个模块可以采用python数据等级制,把数据转换为字符串表达;这个过程称作连接化serializing。重构来自字符串表达式的数据被称为解连接化。在连接化和解连接化之间,表达对象的字符串也许已经储存在一个文件或者数据之中,或者送到了一个网络连接到另外一些远程的机器。
注意:这个JSON格式普通用来借助现代应用而允许数据交换。许多程序员对这个指令已经很熟悉,它用于交互式的操作是一个很好的选择。
如果你有一个对象x, 那么,你就可以用一个简单的编码行去观察其JSON字符串。

>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

函数dump(x, f)的另一种变体,称作dump(),给文本文件连接化其对象。所以,如果f是一个对于为编写打开的文本对象,我们可以做到这一点:

>>> json.dump(x, f)

为了再次解码这个对象,如果它是一个文本文件对象,这个对象已经为读取而打开:

>>> x = json.load(f)

这个简单的连接化技巧可以处理列表和字典,但是连接化在JSON中的任意类别例子,需要点额外的努力。对json模块的参考含有这样一个解释:
也请参看:pickle-pickle模块
和JSON相对,pickle是一项协议,该协议允许任意复杂的python对象连接化。如下所述,这个协议对于python有特别之处,不能够用作与其它语言编写的应用沟通。使用缺省,则是不稳定的:解连接化pickle数据,若来自一个不受信任地方的解连接化pickle,它可以任意地处置编码,如果该数据被一个熟练的黑客入侵者做成文件的话,也会被任意处置。

猜你喜欢

转载自blog.csdn.net/weixin_41670255/article/details/109069058