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會被彙整成元組放進1個星號的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} 的形式放進2個星號的kwargs。由於沒有多餘的位置引數,args會是空元組 ()。若以為x=9會放進b,就會變成 (1, 9, (), {}),但關鍵字引數只會放進名稱一致的形式參數。若以為9會放進args,就會變成 (1, 2, (9,), {}),但具名傳入的值不會歸到位置引數的餘數中。若把1個星號接收字典、2個星號接收元組搞混,就會誤以為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那一行時建立1次,該同一個列表會被保存在函式物件中。並不是每次呼叫都會建立新的空列表,因此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行,由於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那一行只建立1次的那個列表本身,經過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那一行的時間點求值1次。當時的i是5,因此5會被保存到函式物件中。之後即使把i改成10,已保存的預設值也不會改變,因此h()回傳5。另一方面,h(i)是在呼叫的時間點讀取i再傳入,因此會回傳當時的值10。若以為預設值每次呼叫都會重新求值,2行就會都是10;若以為傳入的引數也會被預設值取代,2行就會都是5。可變預設引數的陷阱,也正是源自這條相同的規則。
第6題 | 引數的排列順序
把下列程式碼存成檔案並用python3執行。會發生什麼事?
def g(a=1, b): return a + b print(g(2, 3)) 定義時發生SyntaxError,1行都不會執行 顯示4,並正常執行到最後結束 呼叫時發生TypeError,執行到此停止 顯示5,並正常執行到最後結束 正確答案 A. 定義時發生SyntaxError,1行都不會執行 不能把不帶預設值的形式參數,放在帶有預設值的形式參數後面。這項規則在語法階段就會被檢查,因此執行檔案時會先發生SyntaxError,出現non-default argument follows default argument這樣的訊息。由於語法解析是在執行前對整個檔案進行的,因此連print那一行在內,1行都不會執行。這與呼叫方式錯誤所導致的TypeError,被偵測到的階段完全不同。g(2, 3)本身在形式參數數量上是吻合的,若定義正確應該會顯示5,但根本到不了那一步。若要正確寫法,應像 def g(b, a=1) 那樣,把帶有預設值的放到後面。
第7題 | 函式的回傳值
以下列方式呼叫return有無不同的2個函式。顯示的內容是哪一項?
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,這並不是回傳2個值,而是回傳了 (1, 2) 這1個元組。因此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,), {}) 在呼叫端於列表前加上1個星號,其內容就會被逐一分配為位置引數。因此a是1、b是2,多出來的3會被彙整成元組放進1個星號的args,變成 (3,)。由於只有1個元素,因此末尾帶逗號。由於沒有傳入關鍵字引數,kwargs是空字典 {},不會是None。若忘記加星號寫成f(vals),列表本身就會被放進第1個形式參數,變成 ([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是不可改變的值,即使只求值1次也不會產生共用的問題。省略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。若以為函式內的賦值會影響到外部,2行就會都是local,但那需要在函式開頭宣告global x。反之若以為函式內的賦值會被忽略、讀到外部的值,2行就會都是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那1行。順帶一提,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這3者全部都會被帶入 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兩次時的行為,下列何者正確?
第2次也會從頭重新讀取模組主體並再次執行 第2次會建立不同的物件,狀態不再共用 第2次會使用快取,模組主體不會重新執行 第2次會發生ImportError,相同名稱只能使用一次 正確答案 C. 第2次會使用快取,模組主體不會重新執行 已載入過1次的模組會被快取在名為sys.modules的字典中。第2次import只是把該處已登記的同一個模組物件重新綁定到名稱上,不會發生檔案的重新讀取,也不會重新執行主體。若在模組最上層寫上print,即使import兩次也只會顯示1次,可以藉此確認這一點。即使用別名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回傳的是列表。若以為不把換行當作分隔、只把整個檔案包成1個元素的列表,就會變成 ['one\ntwo\nthree\n'],但readlines會在每個換行處分割元素。用 for line in f 逐行取出時,傳來的行同樣也帶有換行符。
第23題 | 第2次的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 檔案物件會記住目前的位置。第1次read會從頭讀到尾,因此a會存入含換行符在內的14個字元,位置移到末尾。第2次read會試圖從末尾開始讀取,由於已無剩餘內容,會回傳空字串。因此len(a)是14,b == ''是True。若以為第2次也會回傳相同內容,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的目的,就是即使忘記寫關閉的處理,也能確保一定會執行,即使區塊內發生例外而中途跳出,也會被關閉。用逗號分隔,也能用1個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。依序來看,第1次以'w'開啟寫入AAA,接著的'a'是追加,因此變成AAABBB。然而第3次以'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就會連續2次都是0;若以為seek不生效,tell就會連續2次都是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傳入False,則會以原本的文字輸出。
練習:作答本頁的題目 這是隨機出題的練習工具(在啟用 JavaScript 時運作)。即使不使用此工具,也能閱讀上方的所有題目與解說。
※ 解說是供學習用的資訊。考試的出題範圍與制度每年可能變動,請務必確認主辦機構的官方公告。
本頁面譯自日文原文。若譯文與原文內容不一致,以日文版為準。 查看日文原文