Karinoya 学习室 资格考试 Cloud / AI / Python 合格实验室 函数、模块与输入输出
日本語 English Français Bahasa Indonesia 한국어 Română ไทย Tiếng Việt 简体中文 繁體中文
资格考试 · Cloud / AI / Python 合格实验室
函数、模块与输入输出 可以用简体中文阅读题目和解说。讲义(解说文章)仅有日文版。
查看日文版(含讲义) →
第1题 | 参数的接收
只用位置参数调用以下这个带星号形参的函数。显示的内容是哪一项?
def f(a, b=2, *args, **kwargs): return (a, b, args, kwargs) print(f(1, 3, 4, 5)) (1, 3, (4, 5), None) (1, 3, (4, 5), {}) (1, 2, (3, 4, 5), {}) (1, 3, [4, 5], {}) 正确答案 B. (1, 3, (4, 5), {}) 位置参数会按从左到右的顺序对应,因此 a 得到1,b 得到3。b 虽有默认值2,但由于传入了值,就以传入的为准。没能对应完的4和5会汇总到单星号的 args 中作为元组,成为 (4, 5)。由于没有传入任何关键字参数,kwargs 是空字典 {},不会是 None。如果认为 b 会保留默认值2,就会得到 (1, 2, (3, 4, 5), {}),但位置参数的优先级高于默认值。args 中存放的是元组而非列表,因此也不会以方括号显示。
第2题 | 关键字指定
以下调用用关键字传入了定义中没有的名字。显示的内容是哪一项?
def f(a, b=2, *args, **kwargs): return (a, b, args, kwargs) print(f(1, x=9)) (1, 9, (), {}) (1, 2, (), {'x': 9}) (1, 2, (9,), {}) (1, 2, {'x': 9}, {}) 正确答案 B. (1, 2, (), {'x': 9}) 位置参数只有1个,因此 a 得到1,b 没有被传入,保持默认值2。名为 x 的形参在定义中不存在,因此关键字参数 x=9 会以 {'x': 9} 的形式存入双星号的 kwargs。位置参数没有多余的,因此 args 是空元组 ()。如果认为 x=9 会进入 b,就会得到 (1, 9, (), {}),但关键字参数只会进入名字一致的形参。如果认为9会进入 args,就会得到 (1, 2, (9,), {}),但按名字传入的值不会分流到位置参数的余量中。如果把单星号接收字典、双星号接收元组这一点搞反,就会误以为 args 中有 {'x': 9} 而 kwargs 为空。
第3题 | 默认的空列表
把默认值写成空列表的以下函数被调用了3次。显示的3行是哪一项?
def add(x, lst=[]): lst.append(x) return lst print(add(1)) print(add(2)) print(add(3)) 从上到下依次显示 None / None / None 从上到下依次显示 [1] / [1, 2] / [1, 2, 3] 从上到下依次显示 [1] / [2] / [3] 从上到下依次显示 [1] / [1, 2] / [2, 3] 正确答案 B. 从上到下依次显示 [1] / [1, 2] / [1, 2, 3] 默认值 [] 只在执行 def 那一行时被创建一次,同一个列表会被保存在函数对象中。并不是每次调用都会新建一个空列表,因此 append 的结果会保留到下一次调用,依次变为 [1]、[1, 2]、[1, 2, 3]。如果认为每次调用都会得到新列表,就会得到 [1] / [2] / [3],但那是把 lst=None 作为默认值、在函数体内重新创建的行为。如果把函数体误写成 return lst.append(x),append 的返回值 None 会连续出现3次。此外若不分成3行、写成一行 print(add(1), add(2), add(3)),由于三者指向同一个列表,会在全部求值完毕后把 [1, 2, 3] 重复显示3次。
第4题 | 默认值的共享
调用3次之后显示函数对象的属性。显示的内容是哪一项?
def add(x, lst=[]): lst.append(x) return lst add(1) add(2) add(3) print(add.__defaults__) 输出类似 ((1, 2, 3),) 的内容 输出类似 ([],) 的内容 输出类似 [[1, 2, 3]] 的内容 输出类似 ([1, 2, 3],) 的内容 正确答案 D. 输出类似 ([1, 2, 3],) 的内容 函数对象会把默认值以元组的形式保存在名为 __defaults__ 的属性中。形参中只有 lst 带有默认值,因此会是只有1个元素的元组,末尾带逗号的形式显示。其内容正是在 def 那一行只创建过一次的那个列表本身,3次调用中被 append 的结果会原样保留,因此是 ([1, 2, 3],)。若一次都没调用过,则是 ([],),调用带来的影响会体现在这里,这正是可变默认参数的可怕之处。__defaults__ 本身是元组,因此不会以方括号显示,其中的列表也不会变成元组。
第5题 | 默认值的求值时机
在默认值中写了变量,之后又改写了该变量。显示的2行是哪一项?
i = 5 def h(x=i): return x i = 10 print(h()) print(h(i)) 先5后5 先5后10 先10后10 先10后5 正确答案 B. 先5后10 默认值的表达式不是每次调用函数时求值,而是只在 def 那一行被执行时求值一次。此时 i 是5,因此5会被保存到函数对象中。之后即使把 i 改写为10,已保存的默认值也不会改变,因此 h() 返回5。而 h(i) 会在调用时读取当时的 i 再传入,因此返回的是当时的值10。如果认为默认值每次调用都会重新求值,两行都会得到10;如果认为传入的参数值也会被替换成默认值,两行都会得到5。可变默认参数的陷阱,正是从这同一条规则产生的。
第6题 | 参数的排列顺序
把以下代码保存到文件并用 python3 执行。会发生什么?
def g(a=1, b): return a + b print(g(2, 3)) 定义时发生 SyntaxError,一行也不执行 显示4,然后正常执行到最后 调用时发生 TypeError,执行到此停止 显示5,然后正常执行到最后 正确答案 A. 定义时发生 SyntaxError,一行也不执行 带默认值的形参之后,不能再放不带默认值的形参。这一规则在语法阶段就会被检查,因此执行文件时首先会发生 SyntaxError,并给出 non-default argument follows default argument 这样的提示。语法解析是在执行前针对整个文件进行的,因此包括 print 那一行在内,一行也不会被执行。这与因调用方式错误而产生的 TypeError,检测阶段完全不同。g(2, 3) 本身与形参个数是吻合的,如果定义正确,本应显示5,但根本到不了那一步。要正确书写,应把带默认值的形参放到后面,如 def g(b, a=1)。
第7题 | 函数的返回值
以下这样调用有无 return 不同的两个函数。显示的内容是哪一项?
def one(): pass def two(): return 1, 2 print(one(), type(two()).__name__) None tuple None list () list () tuple 正确答案 A. None tuple one 一次也没有经过 return 就结束,因此返回值是 None。不会返回空元组或空字符串。two 在 return 后面用逗号并列了1和2,这不是返回两个值,而是返回了一个元组 (1, 2)。因此 type(two()).__name__ 是 tuple。如果认为多个返回值会被汇总成列表,就会得到list,但用逗号并列的表达式构造的是元组。在把显示出来的函数返回值赋值使用时卡壳,往往正是因为这个 None。
第8题 | 解包调用
在列表前加星号进行调用的以下代码,显示的内容是哪一项?
def f(a, b=2, *args, **kwargs): return (a, b, args, kwargs) vals = [1, 2, 3] print(f(*vals)) ([1, 2, 3], 2, (), {}) (1, 2, (3,), None) (1, 2, [3], {}) (1, 2, (3,), {}) 正确答案 D. (1, 2, (3,), {}) 在调用一方给列表前加上单星号,其内容会作为位置参数逐个分发。因此 a 得到1,b 得到2,多出的3会汇总到单星号的 args 中作为元组,成为 (3,)。因为只有1个元素,末尾带逗号。没有传入关键字参数,因此 kwargs 是空字典 {},不会是 None。如果忘了加星号写成 f(vals),列表本身会整体进入第一个形参,变成 ([1, 2, 3], 2, (), {}),不会报错但结果会不知不觉地改变。args 中存放的是元组,因此不会以方括号显示。
第9题 | 类型注解的处理方式
用与注解所写类型不同的值调用以下函数。会发生什么?
def ann(a: int, b: str = "x") -> bool: return a print(ann("文字列", 999)) print(ann.__annotations__['a']) 第1行显示 文字列,第2行显示 <class 'int'> 第1行发生 TypeError,提示不能传入与注解不同的类型 第1行显示 文字列,第2行显示 int 第1行发生 ValueError,提示无法转换为int 正确答案 A. 第1行显示 文字列,第2行显示 <class 'int'> 注解只会被记录到函数对象名为 __annotations__ 的字典中,运行时不会对类型进行检查。即使 a 写了int,也能传入字符串;即使 b 写了str,也能传入整数。即使返回值写了bool,实际返回的也就是接收到的那个字符串本身。因此第1行会原样显示 文字列(字符串)。第2行的 __annotations__['a'] 中存放的是 int 这个类型对象,因此 print 出来会显示为 <class 'int'> 这样的形式,而不是只显示int这3个字符。想真正检查类型,需要使用其他检查工具,或在函数体内自行确认。
第10题 | 默认值的修正方法
把默认值改成 None 重写后的以下代码,显示的3行是哪一项?
def add2(x, lst=None): if lst is None: lst = [] lst.append(x) return lst print(add2(1)) print(add2(2)) print(add2(3, [9])) 从上到下依次显示 [1] / [1, 2] / [9, 3] 从上到下依次显示 [1] / [2] / [9, 3] 从上到下依次显示 [1] / [2] / [3, 9] 从上到下依次显示 [1] / [2] / [3] 正确答案 B. 从上到下依次显示 [1] / [2] / [9, 3] 把默认值设为None、在函数体开头判断是否为None再创建新的空列表,是常见做法。None是不可变的值,即使只求值一次也不会产生共享的问题。省略 lst 的第1次和第2次调用,每次都会创建不同的空列表,因此分别得到[1]和[2],上一次调用的元素不会残留。第3次调用方传入了[9],因此 lst is None 为假,会把3添加到传入列表的末尾,得到[9, 3]。如果不改写、仍保持 lst=[] ,省略参数的第2次调用会残留上次的元素,得到[1, 2]。如果忽略调用方传入的列表、总是新建列表,就会得到[1] / [2] / [3],这样就失去了这一做法『传入时就使用传入内容』的优点。如果不用添加到末尾的append,而是用插入到开头的insert(0, x),第3次会变成[3, 9]。
第11题 | 名字的查找顺序
Python查找变量名所遵循的LEGB顺序,正确的是哪一项?
按正在执行的函数、模块最上层、外层函数、内置 的顺序查找 按正在执行的函数、外层函数、模块最上层、内置 的顺序查找 按模块最上层、内置、正在执行的函数、外层函数 的顺序查找 按内置、模块最上层、外层函数、正在执行的函数 的顺序查找 正确答案 B. 按正在执行的函数、外层函数、模块最上层、内置 的顺序查找 LEGB是Local(局部)、Enclosing(外层)、Global(全局)、Built-in(内置)的首字母,表示从最内层向外查找。首先查看正在执行的函数内部,接着查看包裹自己的外层函数内部,然后是模块最上层,最后才是len、print等内置。一旦找到就结束查找,因此内层若使用相同名字,就会遮蔽外层的名字。如果按从外向内的顺序查找,函数内创建的局部变量就会输给模块最上层的同名变量,这与实际行为不符。把Global和Enclosing的顺序对调也是常见的错误,但外层函数要比模块最上层更靠内。
第12题 | 局部赋值
在函数内对与外部相同的名字进行赋值。以下代码显示的2行是哪一项?
x = 'global' def f(): x = 'local' print(x) f() print(x) 先 local 后 local 先 local 后 global 先 global 后 global 先 global 后 local 正确答案 B. 先 local 后 global 在函数内对某个名字赋值,就会新建一个同名的局部变量,模块最上层的同名变量完全不受影响。函数内的print(x)读取的是局部的x,因此显示local;退出函数后的print(x)读取的是模块最上层的x,因此显示global。如果认为函数内的赋值会影响到外部,两行都会得到local,但那需要在函数开头声明global x。反过来,如果认为函数内的赋值被忽略、读取的是外部的值,两行都会得到global,但既然赋了值,局部变量确实已被创建。这种『读取能抵达外部,但写入抵达不了』的不对称,正是Python作用域的基本规则。
第13题 | nonlocal
以下代码试图从内层函数改写外层变量。显示的内容是哪一项?
def outer(): n = 1 def inner(): nonlocal n n = 2 inner() return n def outer2(): n = 1 def inner(): n = 2 inner() return n print(outer(), outer2()) 1 2 2 2 2 1 1 1 正确答案 C. 2 1 outer的inner中写有nonlocal n,因此对n的赋值会抵达外一层的outer的n。调用inner之后,outer返回的n是2。outer2的inner中没有声明,因此 n = 2 只会新建inner的局部变量,outer2的n保持1不变。因此显示2 1。如果认为两者都能抵达,会得到2 2;如果认为两者都抵达不了,会得到1 1,但造成差异的正是nonlocal这一行。此外nonlocal只有在外层函数中已存在同名变量时才能使用,也无法指向模块最上层的变量。想改写最上层的变量应使用global。
第14题 | 未绑定错误
在赋值所在行之前的一行读取该名字。执行以下代码会发生什么?
z = 100 def bad(): print(z) z = 1 bad() 发生 UnboundLocalError,什么都不显示就停止 显示100,之后正常执行到最后 显示None,之后正常执行到最后 显示1,之后正常执行到最后 正确答案 A. 发生 UnboundLocalError,什么都不显示就停止 Python在编译函数体的时刻,就会把该函数内含有赋值的名字全部判定为局部变量。这一判定是针对整个函数进行的,与代码行的先后顺序无关。在这个bad中,后面一行有 z = 1 这样的赋值,因此z会被当作这个函数的局部变量。于是在 print(z) 的时刻还没有赋值,就会发生 UnboundLocalError。这个异常的具体提示文字会随Python版本变化,但类型名UnboundLocalError是不变的。即使最上层有 z = 100,也不会被引用。如果认为因为在赋值之前,所以会读取外部的值,就会得到100,但Python并不是这样运作的。没有赋值的局部变量也不会变成None。
第15题 | global声明
并列了带global声明的函数与不带声明的函数。以下代码显示的内容是哪一项?
n = 10 def f(): global n n = 20 return n def g(): n = 30 return n print(f(), g(), n) 20 30 30 20 30 20 20 30 10 10 30 10 正确答案 B. 20 30 20 f中写有global n,因此 n = 20 会改写模块最上层的n。f自身返回20,最上层的n也变为20。g中没有声明,因此 n = 30 只会新建g的局部变量,不影响最上层的n。g返回自己局部的30。最后的print读取的n是最上层的那个,由于f已经改写过,因此是20。如果认为g的赋值也会抵达外部,就会得到20 30 30;如果认为global不起作用,就会得到20 30 10。还应注意,global声明是『把对这个名字的赋值传达到最上层』的指示,如果只是读取值,则不需要声明。
第16题 | __all__的效果
准备以下mymod.py,并在另一个文件中写 from mymod import *。会被带入调用方的名字是哪一项?
# mymod.py __all__ = ['pub'] pub = 'P' other = 'O' def hello(): return 'H' pub、other、hello三者全部被带入 pub和hello被带入,other不会被带入 other和hello被带入,pub不会被带入 只有pub被带入,other和hello都不会被带入 正确答案 D. 只有pub被带入,other和hello都不会被带入 用星号导入通常会把所有不以下划线开头的名字都带入,但如果模块一侧有一个名为__all__的字符串列表,就只会限定为其中列出的名字。这个例子中__all__只写了pub,因此other和hello都不会被带入。other虽然不以下划线开头,但既然有__all__,就以那份清单为准。函数也不会因此被特殊对待,因此hello同样不会被带入。__all__只对星号导入起作用,像 from mymod import hello 这样指定名字的导入,或先 import mymod 再写 mymod.hello() 的方式,都不受影响。
第17题 | __name__的值
关于以下mymod2.py,用 python3 mymod2.py 直接执行,与从另一个文件 import mymod2 时,__name__的值的组合是哪一项?
# mymod2.py print('mymod2:', __name__) if __name__ == '__main__': print('直接実行') 直接执行时为mymod2,import时为__main__ 无论直接执行还是import,都为__main__ 直接执行时为__main__,import时为mymod2 无论直接执行还是import,都为mymod2 正确答案 C. 直接执行时为__main__,import时为mymod2 用python3直接执行该文件时,__name__会被赋值为'__main__'。从其他文件import时,则会被赋值为去掉扩展名的文件名,即'mymod2'。因此直接执行时会显示 mymod2: __main__ 和 直接执行 这2行,而被import时只会显示 mymod2: mymod2 这1行。if __name__ == '__main__': 正是利用这一差异,用来区分『只在直接执行时才想运行的处理』这一常见写法。如果把组合弄反,被import时用于验证运行的处理就会被执行,这与这种写法的目的不符。如果无论哪种情况都是同一个值,那就根本无法做出区分。
第18题 | sys.argv
用命令 python3 args.py foo bar 执行以下args.py时,显示的2行是哪一项?
# args.py import sys print(len(sys.argv)) print(sys.argv[1:]) 先显示2,再显示 ['foo', 'bar'] 先显示3,再显示 ['args.py', 'foo'] 先显示3,再显示 ['foo', 'bar'] 先显示2,再显示 ['args.py', 'foo'] 正确答案 C. 先显示3,再显示 ['foo', 'bar'] sys.argv是把命令行传入的值排列成的列表,其开头是脚本名本身。这个例子中会是像 ['args.py', 'foo', 'bar'] 这样的3元素列表,因此len是3。取下标1以后的 sys.argv[1:] 是去掉脚本名的 ['foo', 'bar']。如果认为长度就等于传入参数的个数,会得到2,但这漏数了开头的脚本名。认为 sys.argv[1:] 会是 ['args.py', 'foo'],是把切片起始位置弄错的情况。如果不传任何参数执行,长度会是1,sys.argv[1:] 则是空列表。
第19题 | 模块的重新载入
在同一个程序中对同一个模块 import 两次时,正确的行为是哪一项?
第二次也会重新从头读取并执行模块主体 第二次会创建不同的对象,状态不再共享 第二次会使用缓存,模块主体不会被重新执行 第二次会发生 ImportError,同一个名字只能使用一次 正确答案 C. 第二次会使用缓存,模块主体不会被重新执行 已经加载过的模块会被缓存到名为sys.modules的字典中。第二次import只是把该名字重新绑定到那里登记的同一个模块对象,不会重新读取文件,也不会重新执行主体。如果在模块最上层写一句print,就可以确认即使import两次也只会显示一次。即使起了别名再import,指向的也是同一个对象。因此,只要改写某个模块的变量,其他import了该模块的地方也能看到改写后的值。同一个名字import两次不会报错,如果开发中想重新读取主体,需要显式使用importlib的功能。
第20题 | 虚拟环境的判定
判断正在运行的Python是否处于用venv创建的虚拟环境中,恰当的方法是哪一项?
如果没有定义 sys.prefix,就可判定处于虚拟环境中 如果 sys.prefix 与 sys.base_prefix 一致,就可判定处于虚拟环境中 如果定义了 sys.base_prefix,就可判定处于虚拟环境中 如果 sys.prefix 与 sys.base_prefix 不一致,就可判定处于虚拟环境中 正确答案 D. 如果 sys.prefix 与 sys.base_prefix 不一致,就可判定处于虚拟环境中 sys.prefix指向当前运行的Python的安装位置,sys.base_prefix指向作为其源头的Python所在位置。在虚拟环境之外两者的值相同,但在用 python3 -m venv 创建的环境中,sys.prefix会指向所创建环境的目录,而sys.base_prefix仍指向原来的Python,因此两者会不一致。这种不一致的有无正是判断的依据。认为两者一致就是处于虚拟环境中,是把条件弄反了。sys.base_prefix即使在虚拟环境之外也总是被定义,因此不能靠是否定义来判断,sys.prefix同样总是被定义。具体的路径字符串和pip的版本因环境而异,只需记住这一层关系即可。
第21题 | write的返回值
接收写入方法返回值的以下代码,显示的内容是哪一项?
with open('/tmp/sample.txt', 'w') as f: n = f.write("one\ntwo\nthree\n") print(n) 显示None 显示3 显示14 显示11 正确答案 C. 显示14 write会把写入的字符数作为返回值返回。"one\ntwo\nthree\n"中one是3个字符,two是3个字符,three是5个字符,合计11个字符,再加上3个换行符,总共14个字符。如果忘了数换行符,就会得到11。如果认为返回的是写入的行数,会得到3,但write计的终归是字符数。认为不会返回任何东西而选None也是常见的错误,容易与append或sort这类返回None的方法混淆,但write返回的是数字。此外write不会自动加换行,想要分行时需要自己在字符串中写入。
第22题 | readlines
用readlines读取写有3行的文件。显示的内容是哪一项?
with open('/tmp/sample.txt', 'w') as f: f.write("one\ntwo\nthree\n") with open('/tmp/sample.txt') as f: print(f.readlines()) 显示 ['one\ntwo\nthree\n'] 显示 'one\ntwo\nthree\n' 显示 ['one\n', 'two\n', 'three\n'] 显示 ['one', 'two', 'three'] 正确答案 C. 显示 ['one\n', 'two\n', 'three\n'] readlines会把所有行汇总成列表返回,但各元素行末的换行符会原样保留。因此得到 ['one\n', 'two\n', 'three\n']。如果认为换行符会被去掉,就会得到 ['one', 'two', 'three'],但想要那样的结果需要对各元素应用rstrip。不做分割、作为一个字符串整体返回的是read,readlines返回的是列表。如果认为不把换行当作分隔、只是把整个文件包成一个元素的列表,就会得到 ['one\ntwo\nthree\n'],但readlines会在每个换行处分割元素。用 for line in f 逐行取出时,传来的行同样带有换行符。
第23题 | 第二次read
从同一个文件对象连续读取2次。显示的内容是哪一项?
with open('/tmp/sample.txt') as f: a = f.read() b = f.read() print(len(a), b == '') 显示 14 False 显示 14 True 显示 0 False 显示 0 True 正确答案 B. 显示 14 True 文件对象会记住当前位置。第一次read会从头读到末尾,因此a中存入包含换行符在内的14个字符,位置移动到末尾。第二次read会试图从末尾读取,由于没有剩余内容,返回空字符串。因此len(a)是14,b == ''为True。如果认为第二次也会返回相同内容,b == ''就会是False,但想重新读取相同内容,需要用f.seek(0)把位置移回开头。len(a)变为0的情况是文件本身为空,或位置本来就已在末尾。什么都不显示时容易误以为读取失败,但原因在于位置处于末尾。
第24题 | with与close
在with代码块内外分别确认closed。显示的2行是哪一项?
with open('/tmp/sample.txt') as f: print(f.closed) print(f.closed) 先False后False 先False后True 先True后False 先True后True 正确答案 B. 先False后True 在with代码块内文件还处于打开状态,因此closed为False。退出代码块时会自动关闭,因此之后的closed为True。因此显示的是先False后True。使用with的目的,就是即使忘记写关闭处理,也一定会被执行,即便代码块中发生异常而中途退出,文件也会被关闭。用逗号分隔可以在一个with语句中同时打开多个文件,这种情况下同样都会被关闭。关闭之后再想从该文件对象读取会发生ValueError,因此无法在with之外重新读取。
第25题 | 写入模式
把同一个文件打开3次并写入后再读出。显示的内容是哪一项?
p = '/tmp/sample.txt' with open(p, 'w') as f: f.write('AAA') with open(p, 'a') as f: f.write('BBB') with open(p, 'w') as f: f.write('C') with open(p) as f: print(f.read()) 文件内容是 AAABBB 文件内容是 AAABBBC 文件内容是 C 文件内容是 CAABBB 正确答案 C. 文件内容是 C 以'w'打开时,无论是否已有内容,文件都会被截断为长度0。依次来看:第一次'w'写入AAA,接下来的'a'是追加,因此变为AAABBB。但第三次以'w'打开的瞬间,AAABBB会被清除,然后写入C,因此最终只剩下C。如果认为3次都是以'a'打开,会得到AAABBBC;如果认为最后一次写入被忽略,会得到AAABBB。不截断、从开头覆盖写入的是'r+'的行为,那种情况下会像CAABBB那样只替换开头的1个字符。截断发生在打开的瞬间而非写入的瞬间,因此即使只用'w'打开而不写入任何内容,文件也会变空。如果打算追加就用'a',打算重新做一份就用'w',用模式来表明意图。
第26题 | 关闭之后的操作
试图读取已经close的文件对象,会发生什么?
f = open('/tmp/sample.txt') f.close() print(f.read()) 发生 ValueError,提示这是对已关闭文件的操作 显示空字符串,然后正常执行结束 自动重新打开,显示文件的全部内容 发生 OSError,提示无法重新打开文件 正确答案 A. 发生 ValueError,提示这是对已关闭文件的操作 对已经close的文件对象调用read会发生ValueError,并附带 I/O operation on closed file. 这样的提示。是否已关闭由对象自身记住,因此不会因为需要而自动重新打开。也不是因为没有内容可读才返回空字符串——那是读到末尾之后read的行为。文件找不到时的FileNotFoundError等属于OSError一族,但操作已关闭文件时的异常与那不同,是ValueError。使用with语句,就能从结构上避免退出代码块后还试图读取这种情况。
第27题 | 只读
试图向省略了模式打开的文件写入,会发生什么?
with open('/tmp/sample.txt', 'w') as f: f.write('data') with open('/tmp/sample.txt') as f: f.write('more') 发生 io.UnsupportedOperation,提示无法写入 发生 ValueError,提示模式指定不正确 内容末尾被添加上more,然后正常执行结束 内容被替换为more,然后正常执行结束 正确答案 A. 发生 io.UnsupportedOperation,提示无法写入 省略open的第2个参数时会是只读的'r'。对以只读方式打开的文件对象调用write会发生io.UnsupportedOperation,并附带not writable的提示。这在语法上是能通过的写法,因此要到执行时才会发现模式弄错了。想覆盖写入应改用'w'重新打开,想追加到末尾应改用'a',想同时读写应改用'r+'重新打开。反过来,对以'w'或'a'打开的文件调用read,同样会发生io.UnsupportedOperation,并提示not readable。可以说模式正是声明该文件对象允许做什么的地方。
第28题 | seek与tell
只从开头读取5个字符并确认位置。以下代码显示的内容是哪一项?
with open('/tmp/sample.txt') as f: head = f.read(5) pos = f.tell() f.seek(0) print(repr(head), pos, f.tell()) 'one\ntwo\nthree\n' 14 0 'one\nt' 5 0 'one\nt' 5 5 'one\nt' 0 0 正确答案 B. 'one\nt' 5 0 给read传入数字,就只会读取那么多字符。开头5个字符是one的3个字符加上1个换行符,再加two的t,因此是'one\nt'。读了多少位置就前进多少,因此紧接着的tell会返回5。接下来的seek(0)把位置移回开头,因此最后的tell是0。如果认为read不会移动位置,两次tell都会是0;如果认为seek不起作用,两次tell都会是5。如果把read的参数误认为是行数而非字符数,就会误以为要读到第5行,从而选中读取整个文件得到的'one\ntwo\nthree\n'和14。使用seek(0),就能在同一个文件对象上从头重新读取。
第29题 | JSON与元组
把含有元组的字典转成JSON再读回。以下代码显示的2行是哪一项?
import json s = json.dumps({'t': (1, 2)}) back = json.loads(s) print(s) print(back, type(back['t']).__name__) 先显示 {"t": "(1, 2)"},再显示 {'t': '(1, 2)'} str 先显示 {"t": [1, 2]},再显示 {'t': (1, 2)} tuple 先显示 {"t": (1, 2)},再显示 {'t': (1, 2)} tuple 先显示 {"t": [1, 2]},再显示 {'t': [1, 2]} list 正确答案 D. 先显示 {"t": [1, 2]},再显示 {'t': [1, 2]} list JSON中只有数组,不存在元组这种类型。因此在dumps的时刻元组就会被转换为数组,输出为 {"t": [1, 2]}。读回时数组也会被还原为列表,因此back['t']的类型是list,不会还原成元组。原本是元组这一信息不会保留在任何地方,因此一来一回类型就会改变。如果想继续以元组处理,需要在读回之后自己应用tuple()。JSON的输出中不会出现圆括号,元组也不会被转换成字符串。出于同样的原因,如果字典的键使用整数,dumps时会被转换成字符串,读回后也不会还原为整数。
第30题 | JSON的键
试图把带整数键的字典和集合转成JSON,会发生什么?
import json print(json.dumps({1: 'a'})) print(json.dumps({1, 2})) 第1行显示 {1: "a"},第2行显示 [1, 2] 第1行发生 TypeError,第2行不会被执行 第1行显示 {"1": "a"},第2行发生 TypeError 第1行显示 {"1": "a"},第2行显示 [1, 2] 正确答案 C. 第1行显示 {"1": "a"},第2行发生 TypeError JSON对象的键必须是字符串,因此把以整数为键的字典dumps时,键会被转换成字符串,输出为 {"1": "a"}。这是能正常成功的处理,不会在此处引发异常。但要注意读回时的键仍是字符串,不会还原成整数。第2行的集合没有对应的JSON类型,因此dumps会引发TypeError,并提示 Object of type set is not JSON serializable。如果认为集合会被写成数组,会得到[1, 2],但如果希望那样转换,需要自己先应用list()再传入。对于包含日语等的值,默认情况下会被转义后输出,若把ensure_ascii传为假,则会按原样的字符输出。
练习:做本页的题目 这是随机出题的练习工具(在启用 JavaScript 时运行)。即使不使用此工具,也可以阅读上方的全部题目和解说。
※ 解说是供学习用的信息。考试的出题范围和制度每年可能变化,请务必确认主办机构的官方公告。
本页面译自日文原文。如译文与原文内容不一致,以日文版为准。 查看日文原文