Python 基础 -- Tutorial(三)

7、输入和输出

有几种方法可以表示程序的输出;数据可以以人类可读的形式打印出来,或者写入文件以备将来使用。本章将讨论其中的一些可能性。

7.1 更花哨的输出格式

到目前为止,我们已经遇到了两种写值的方法:表达式语句print()函数。(第三种方法是使用文件对象的write()方法;标准输出文件可以被引用为sys.stdout。有关这方面的更多信息,请参阅库参考。)

通常,您需要更多地控制输出的格式,而不是简单地打印空格分隔的值。有几种格式化输出的方法。

  • 若要使用格式化字符串字面值,请在字符串的开始引号或三引号前以fF开头。在这个字符串中,你可以在{ }字符之间编写一个Python表达式,它可以引用变量或文字值。
>>> year = 2016
>>> event = 'Referendum'
>>> f'Results of the {
      
      year} {
      
      event}'
'Results of the 2016 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 ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

string模块包含一个Template类,它提供了另一种将值替换为字符串的方法,使用像$x这样的占位符并用字典中的值替换它们,但提供的格式控制要少得多。

7.1.1 格式化字符串字面值

格式化字符串字面值(也简称为f-strings)允许你在字符串前加上fF前缀并将表达式写成{expression},从而将Python表达式的值包含在字符串中。

表达式后面可以有一个可选的格式说明符。这样可以更好地控制值的格式。下面的例子将圆周率四舍五入到小数点后三位:

>>> import math
>>> print(f'The value of pi is approximately {
      
      math.pi:.3f}.')
The value of pi is approximately 3.142.

在’:'后面传递一个整数将导致该字段的宽度为最小字符数。这对于使列对齐很有用。

>>> table = {
    
    'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{
      
      name:10} ==> {
      
      phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

可以使用其他修饰符在对值进行格式化之前对其进行转换!a 应用ascii() !s应用str(),而!r应用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}.')
My hovercraft is full of 'eels'.

有关这些格式规范的参考,请参见格式规范迷你语言的参考指南。

7.1.2 String format()方法

str.format()方法的基本用法如下:

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

大括号和其中的字符(称为格式字段)被传递给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 {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                       other='Georg'))
The story of Bill, Manfred, and Georg.

如果您有一个非常长的格式字符串,并且您不想拆分它,那么您最好可以按名称而不是按位置引用要格式化的变量。这可以通过简单地传递字典并使用方括号’[]'来访问键来实现

>>> table = {
    
    'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

这也可以通过将表作为带有’ **'符号的关键字参数传递来实现

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

这在与内置函数vars()结合使用时特别有用,该函数返回包含所有局部变量的字典。

作为一个例子,下面几行代码生成了一组整齐排列的列,给出了整数及其平方和立方:

>>> 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()格式化字符串的完整概述,请参见格式化字符串语法。

7.1.3 手动字符串格式化

下面是同样的方形和立方体表格,手动格式化:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     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()方法通过在左侧填充空格来对给定宽度字段中的字符串进行右对齐(right-justifies)。还有类似的方法str.ljust()str.center()。这些方法不写任何东西,它们只是返回一个新的字符串。如果输入字符串太长,它们不会截断它,而是原样返回;这将使您的列布局混乱,但这通常比另一种选择要好,后者将对值撒谎。(如果真的需要截断,可以添加切片操作,如x.ljust(n)[:n]。)

还有另一个方法str.zfill(),它在数字字符串的左边填充零。它理解加号和减号:

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

7.1.4 旧的字符串格式

%操作符(取模)也可用于字符串格式化。给定'string' % valuesstring%的实例将被替换为0个或多个值元素。这个操作通常被称为字符串插值。例如:

>>> 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 读写文件

open()返回一个文件对象,最常用的是两个位置参数和一个关键字参数:open(filename, mode, encoding=None)

f = open('workfile', 'w', encoding="utf-8")

第一个参数是包含文件名的字符串。第二个参数是另一个字符串,包含一些描述文件使用方式的字符。mode 可以是’r’,当文件只被读取时,'w’表示只被写入(同名的现有文件将被擦除),'a’打开文件用于追加;写入文件的任何数据都会自动添加到末尾。'r+'打开文件进行读写mode参数是可选的;如果省略,则假定为’r’。

通常,文件是以文本模式打开的,这意味着,您可以从文件中读取和写入字符串,这些字符串以特定的编码(encoding)进行编码。如果未指定encoding,则默认为平台相关(请参阅open())。因为UTF-8是现代事实上的标准,所以建议使用encoding="utf-8",除非您知道需要使用不同的编码。mode 后添加'b'将以二进制模式打开文件。二进制模式数据以字节对象的形式进行读写。以二进制模式打开文件时不能指定encoding

在文本模式下,读取时的默认值是将特定于平台的行结尾(Unix上的\n, Windows上的\r\n)转换为\n。在文本模式下写入时,默认是将\n的出现转换回特定于平台的行结尾。这种对文件数据的幕后修改对于文本文件没有问题,但会破坏JPEGEXE文件中的二进制数据。在读写此类文件时,要非常小心地使用二进制模式。

在处理文件对象时使用with关键字是一个很好的做法。这样做的好处是,文件在其suite 完成后被正确关闭,即使在某些时候引发了异常。使用with也比编写同等的try-finally块要短得多:

>>> with open('workfile', encoding="utf-8") as f:
...     read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed
True

警告:调用f.write()而不使用with关键字或没有调用f.close()可能导致f.write(的参数没有完全写入磁盘,即使程序成功退出。

在使用with语句或调用f.close()关闭文件对象后,尝试使用该文件对象将自动失败。

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

7.2.1 文件对象的方法

本节中的其余示例将假设已经创建了一个名为f的文件对象。

要读取文件的内容,调用f.read(size),它将读取一定数量的数据并以字符串(文本模式)或bytes对象(二进制模式)的形式返回。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 line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

如果你想以列表的形式读取文件的所有行,你也可以使用list(f)f.redlines()

f.write(string)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'

在文本文件中(那些在模式字符串中打开时没有b的文件),只允许相对于文件开头的搜索(使用seek(0,2)查找到文件末尾的例外),唯一有效的offset 值是从f.tell()返回的偏移量值或零。任何其他偏移值都会产生未定义的行为。

文件对象有一些额外的方法,比如isatty()truncate(),它们不太常用;有关文件对象的完整指南,请参阅库参考。

7.2.2 用json保存结构化数据

字符串可以很容易地写入和读取文件。数字需要更多的努力,因为read()方法只返回字符串,必须传递给int()之类的函数,该函数接受'123'之类的字符串并返回其数值123。当您希望保存更复杂的数据类型(如嵌套列表和字典)时,手动解析和序列化会变得很复杂。

Python允许您使用称为JSON (JavaScript Object Notation)的流行数据交换格式,而不是让用户不断编写和调试代码以将复杂的数据类型保存到文件中。名为json的标准模块可以接受Python数据层次结构,并将其转换为字符串表示;这个过程称为序列化(serializing)。从字符串表示重新构造数据称为反序列化(deserializing)。在序列化和反序列化之间,表示对象的字符串可能已经存储在文件或数据中,或者通过网络连接发送到某个远程机器。

现代应用程序通常使用JSON格式进行数据交换。许多程序员已经熟悉它,这使得它成为互操作性的一个很好的选择。

如果你有一个对象x,你可以用一行简单的代码来查看它的JSON字符串表示:

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

dumps()函数的另一个变体称为dump()它简单地将对象序列化为文本文件。因此,如果f是为写入而打开的文本文件对象,我们可以这样做:

json.dump(x, f)

要再次解码该对象,如果f是已打开以供读取的二进制文件或文本文件对象:

x = json.load(f)

注意:JSON文件必须使用UTF-8编码。使用encoding="utf-8"打开JSON文件作为文本文件进行读写。

see also:

pickle——pickle模块

与JSON相反,pickle是一种允许序列化任意复杂Python对象的协议。因此,它是特定于Python的,不能用于与用其他语言编写的应用程序通信。默认情况下,它也是不安全的:如果数据是由熟练的攻击者精心制作的,那么反序列化来自不受信任源的pickle数据可以执行任意代码。

8、错误和异常

到目前为止,错误消息还没有被提及,但是如果您尝试过这些示例,您可能已经看到了一些。有(至少)两种可区分的错误:语法错误(syntax errors)异常(exceptions)

8.1 语法错误

语法错误,也被称为解析错误,可能是你在学习Python时最常见的抱怨:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

解析器重复错误行,并显示一个小“箭头”,指向该行中最早检测到错误的点该错误是由箭头前面的标token 引起的(或至少在箭头处检测到):在示例中,错误是在函数print()处检测到的,因为在它前面缺少冒号(':')。文件名和行号会被打印出来,这样您就知道在输入来自脚本的情况下应该去哪里查找。

8.2 异常

即使语句或表达式在语法上是正确的,当试图执行它时也可能导致错误在执行过程中检测到的错误被称为异常exceptions ),并且不是无条件致命的:您将很快学习如何在Python程序中处理它们。然而,大多数异常不是由程序处理的,并导致如下所示的错误消息:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

错误消息的最后一行表明发生了什么异常有不同的类型,并且类型作为消息的一部分打印出来:示例中的类型是ZeroDivisionErrorNameErrorTypeError。打印为异常类型的字符串是发生的内置异常的名称。这对所有内置异常都是正确的,但对于用户定义的异常不一定是正确的(尽管这是一个有用的约定)。标准异常名是内置标识符(不是保留关键字)

这一行的其余部分提供了基于异常类型和导致异常的原因的详细信息。

错误消息的前一部分以堆栈回溯的形式显示异常发生的上下文。一般来说,它包含一个堆栈回溯列表源行;但是,它不会显示从标准输入读取的行。

内置异常列出了内置异常及其含义。

8.3 处理异常

编写处理选定异常的程序是可能的。看看下面的例子,它要求用户输入,直到输入一个有效的整数,但允许用户中断程序(使用Control-C或操作系统支持的任何东西);注意,用户生成的中断是通过引发KeyboardInterrupt异常来发出信号的。

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops!  That was no valid number.  Try again...")

try语句的工作方式如下:

  • 首先,执行try子句(tryexcept关键字之间的语句)。
  • 如果没有异常发生,则跳过except子句,并完成try语句的执行。
  • 如果在执行try子句期间发生异常,则跳过子句的其余部分。然后,如果它的类型匹配以except关键字命名的异常,则执行except子句,然后在try语句之后继续执行。
  • 如果发生的异常与except子句中指定的异常不匹配,则将其传递给外部try语句;如果没有找到处理程序,则它是一个未处理的异常(unhandled exception),执行将停止,并显示如上所示的消息。

try语句可以有多个except子句,以指定不同异常的处理程序最多将执行一个处理程序处理程序只处理在相应的try子句中发生的异常,而不是在同一try语句的其他处理程序中发生的异常except子句可以将多个异常命名为带括号的元组,例如:

... except (RuntimeError, TypeError, NameError):
...     pass

except子句中的类如果是相同的类或其基类,则与异常兼容(但不是相反-except子句中列出派生类与基类不兼容)。例如,下面的代码将按此顺序打印B, C, D:

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

注意,如果except子句颠倒过来(首先是except B),它将打印B, B, B——第一个匹配的except子句将被触发。

最后一个except子句可以省略异常名,用作通配符。使用这种方法时要格外小心,因为这种方法很容易掩盖真正的编程错误!它也可以用来打印错误消息,然后重新引发异常(允许调用者也处理异常):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

try … except语句有一个可选的else子句,当它出现时,必须跟在所有except子句之后对于在try子句没有引发异常的情况下必须执行的代码,它很有用。例如:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

使用else子句比在try子句中添加额外的代码要好,因为它可以避免意外捕获由try…except语句保护的代码所没有引发的异常。

当异常发生时,它可能有一个关联值,也称为异常的参数(argument)。参数的存在和类型取决于异常类型。

except子句可以在异常名之后指定一个变量。该变量绑定到一个异常实例,其参数存储在instance.args中。为方便起见,异常实例定义了__str__(),这样就可以直接打印参数,而不必引用.args还可以在引发异常之前先实例化异常,并根据需要为其添加任何属性。

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    # the exception instance
...     print(inst.args)     # arguments stored in .args
...     print(inst)          # __str__ allows args to be printed directly,
...                          # but may be overridden in exception subclasses
...     x, y = inst.args     # unpack args
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

如果一个异常有参数,它们将被打印为未处理异常消息的最后一部分(’ detail ')。

异常处理程序不仅处理在try子句中立即发生的异常,还处理在try子句中调用(甚至间接)的函数中发生的异常。例如:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as err:
...     print('Handling run-time error:', err)
...
Handling run-time error: division by zero

8.4 抛出异常

raise语句允许程序员强制发生指定的异常。例如:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

要引发的唯一参数指示要引发的异常。这必须是一个异常实例或一个异常类(从exception派生的类)如果传递一个异常类,它将通过不带参数调用其构造函数来隐式实例化:

raise ValueError  # shorthand for 'raise ValueError()'

如果你需要确定异常是否被引发,但不打算处理它,raise语句的一种更简单的形式允许你重新引发异常:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

8.5 异常链

raise语句允许一个可选的from,该语句允许链接异常。例如:

# exc must be exception instance or None.
raise RuntimeError from exc

这在转换异常时非常有用。例如:

>>> def func():
...     raise IOError
...
>>> try:
...     func()
... except IOError as exc:
...     raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in func
OSError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database

异常链接在exceptfinally块中引发异常时自动发生。异常链可以通过使用from None来禁用:

try:
    open('database.sqlite')
except OSError:
    raise RuntimeError from None

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError

有关链接机制的更多信息,请参见内置异常

8.6 用户自定义异常

程序可以通过创建一个新的异常类来命名它们自己的异常(有关Python的更多信息,请参阅类)。异常通常应该直接或间接地从Exception类派生。

可以定义异常类,它可以做任何其他类可以做的事情,但通常保持简单,通常只提供一些属性,这些属性允许处理程序为异常提取有关错误的信息。

大多数异常的命名都以" Error "结尾,类似于标准异常的命名。

许多标准模块定义了它们自己的异常,以报告它们定义的函数中可能发生的错误。关于类的更多信息见一章。

8.7 定义清理操作

try语句还有另一个可选子句,用于定义在所有情况下都必须执行的清理操作。例如:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

如果存在finally子句,finally子句将作为try语句完成之前的最后一个任务执行。无论try语句是否产生异常,finally子句都会运行。以下几点讨论了发生异常时更复杂的情况:

  • 如果在执行try子句期间发生异常,则可以使用except子句处理该异常。如果异常没有由except子句处理,则在finally子句执行后重新引发异常。
  • 异常可能在执行exceptelse子句期间发生。同样,在finally子句执行之后,异常被重新引发。
  • 如果finally子句执行了一个breakcontinuereturn语句,则不会重新引发异常。
  • 如果try语句到达breakcontinuereturn语句,finally子句将在breakcontinuereturn语句执行之前执行。
  • 如果finally子句包含返回语句,则返回值将是finally子句返回语句中的值,而不是try子句返回语句中的值。

例子:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

一个更复杂的例子:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

如您所见,finally子句在任何情况下都会执行。通过分割两个字符串引发的TypeError不会由except子句处理,因此会在finally子句执行后重新引发。

在实际应用程序中,finally子句对于释放外部资源(如文件或网络连接)非常有用,而不管资源的使用是否成功。

8.8 预定义的清理操作

一些对象定义了在不再需要该对象时执行的标准清理操作,而不管使用该对象的操作是成功还是失败。请看下面的示例,该示例尝试打开一个文件并将其内容打印到屏幕上。

for line in open("myfile.txt"):
    print(line, end="")

这段代码的问题是,在这部分代码完成执行之后,它会使文件打开一段不确定的时间。这在简单的脚本中不是问题,但对于较大的应用程序可能是一个问题。with语句允许像文件这样的对象以一种确保它们总是被及时正确地清理的方式使用。

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

语句执行后,文件f总是关闭,即使在处理行时遇到问题。像文件一样,提供预定义清理操作的对象将在其文档中指出这一点。

猜你喜欢

转载自blog.csdn.net/chinusyan/article/details/132336914