Karinoya 學習室

證照 · Cloud / AI / Python 合格實驗室

例外、類別與標準函式庫

可以用繁體中文閱讀題目與解說。講義(解說文章)僅有日文版。

查看日文版(含講義) →

第1題 | try陳述式的順序

執行下列程式碼時,由上而下排列輸出的行是哪一項?

def calc(a, b):    try:        print("try")        r = a / b    except ZeroDivisionError:        print("except")    else:        print("else")    finally:        print("finally")calc(9, 3)
  1. try、else、except
  2. try、else、finally
  3. try、except、else
  4. try、except、finally
正確答案B. try、else、finally

9 / 3 不會產生例外,因此不會進入except。只有在沒有發生例外時才會執行的是else子句,因此會在try之後執行else。finally子句不論有無例外都一定會執行,因此最後才會執行。因此會是try、else、finally這3行。含有except的選項,是誤以為發生了例外時的排列,這個呼叫並不會發生ZeroDivisionError。缺少finally的選項,則是把finally誤解成「發生例外時的收尾」時的排列。

第2題 | else子句

關於try陳述式的else子句被執行的條件,下列何者正確?

  1. 不論有無例外,永遠最後執行
  2. 只有在try子句發生例外時才會執行
  3. 在沒有符合任何except子句時執行
  4. 只有在try子句沒有發生例外時才會執行
正確答案D. 只有在try子句沒有發生例外時才會執行

try陳述式的else子句,只有在try子句一路執行到最後都沒有拋出例外時才會執行。與if陳述式的else是「條件為假時」相對,try陳述式的else則是搭配「順利成功時」的一方,方向相反,這樣記憶比較好。發生例外時執行的是except子句。不論有無例外都一定會執行的則是finally子句。若沒有符合任何except,並不會落入else,而是該例外會傳播到呼叫端。

第3題 | finally優先

執行下列程式碼時的輸出是哪一項?

def f():    try:        return "T"    finally:        print("F")def g():    try:        return "T"    finally:        return "N"print(f(), g())
  1. 輸出F與T T這2行
  2. 輸出F與T N這2行
  3. 輸出T T與F這2行
  4. 輸出T N與F這2行
正確答案B. 輸出F與T N這2行

f()準備好要return的值之後,在實際回傳之前會先執行finally子句。因此F會先顯示,回傳值仍維持T。g()的finally子句中含有return,這會覆蓋try子句的return,因此回傳值是N。print會先對f()與g()求值,再彙整成1行顯示,因此F那一行在前、T N那一行在後。變成T T的選項,是誤以為finally的return會被忽略;F排在後面的選項,則是誤以為finally會在return之後才執行時的答案。

第4題 | 無法捕捉的例外

以except Exception所寫的except子句無法捕捉的例外是哪一項?

  1. FileNotFoundError
  2. ZeroDivisionError
  3. KeyboardInterrupt
  4. ModuleNotFoundError
正確答案C. KeyboardInterrupt

KeyboardInterrupt直接位於BaseException之下,並未繼承Exception。issubclass(KeyboardInterrupt, Exception)會是False。這是使用者試圖用Ctrl-C中止時所產生的,為了避免被寫得較廣泛的except Exception不小心吞掉,因此把階層分開了。SystemExit也位於相同的位置。其餘3者都是Exception的子孫:ZeroDivisionError在ArithmeticError之下,FileNotFoundError在OSError之下,ModuleNotFoundError在ImportError之下,因此都能被except Exception捕捉。

第5題 | 例外的父類別

IndexError與KeyError共通的最近父類別是哪一項?

  1. ReferenceError
  2. ArithmeticError
  3. AttributeError
  4. LookupError
正確答案D. LookupError

IndexError與KeyError都具有「用索引或鍵去查找卻找不到」這種相同的性質,因此被放在名為LookupError的共通父類別之下。只要寫 except LookupError,就能同時接住列表超出範圍的存取,以及字典中不存在的鍵這2種情況。ArithmeticError是ZeroDivisionError等的父類別,用於計算本身無法成立的場合。AttributeError是找不到屬性時,ReferenceError則是弱參照所指對象已消失時的例外,兩者都直接位於Exception之下,與LookupError是不同的系統。

第6題 | except的順序

執行下列程式碼時的輸出,下列何者正確?

try:    x = 8 / 0except ArithmeticError:    print("A")except ZeroDivisionError:    print("Z")
  1. 什麼都不輸出就停止
  2. 只輸出Z
  3. 輸出A與Z
  4. 只輸出A
正確答案D. 只輸出A

8 / 0會拋出ZeroDivisionError,而ZeroDivisionError是ArithmeticError的子類別。except子句會由上而下依序比對,在第一個吻合處就中止,因此先寫的except ArithmeticError會先接住,只輸出A。後面的except ZeroDivisionError不會有機會執行。由於吻合的except只會執行1個,因此不會同時輸出A與Z。原則是把範圍窄的(子類別)放在前面、範圍廣的(父類別)放在後面,本題的程式碼正好把這個順序顛倒了。

第7題 | 多個例外

執行下列程式碼時顯示的1行是哪一項?

def conv(s):    try:        return int(s) * 2    except (ValueError, TypeError) as e:        return type(e).__name__print(conv("21"), conv("abc"), conv(None))
  1. 42 ValueError TypeError
  2. 42 TypeError TypeError
  3. 42 ValueError ValueError
  4. 42 TypeError ValueError
正確答案A. 42 ValueError TypeError

在except後面寫上用圓括號括起來的元組時,只要吻合其中之一就會執行。conv("21")由於 int("21") 是21,因此回傳42。conv("abc")雖然能作為字串傳入,但無法解讀為數值,因此是ValueError。conv(None)由於型別不符合int()可接受的類型,因此是TypeError。型別正確但值不合適是ValueError、型別本身就不符合則是TypeError,這樣的區分直接對應答案的排列。

第8題 | 重新拋出

使用自訂例外與不帶引數的raise,下列程式碼的輸出是哪一項?

class LoadError(Exception):    passdef load():    try:        raise LoadError("bad")    except LoadError:        print("inner")        raisetry:    load()except Exception as e:    print("outer", type(e).__name__)
  1. 只顯示inner,不顯示outer
  2. 顯示inner與outer Exception這2行
  3. 顯示inner與outer LoadError這2行
  4. 顯示inner後,於LoadError停止
正確答案C. 顯示inner與outer LoadError這2行

在except內只寫raise而不帶引數,會把目前正在處理的例外原樣重新拋回呼叫端。因此顯示inner之後,LoadError會傳到load()外部,由外層的except Exception接住。由於LoadError繼承自Exception,能被except Exception捕捉,因此不會停止。type(e).__name__回傳的是實際被拋出之例外類別的名稱,因此顯示的不是捕捉端的型別名稱Exception,而是LoadError。只顯示inner的情況,是沒有重新拋出、而是當場吞掉時的輸出。

第9題 | 未捕捉的例外

執行下列程式碼會得到什麼結果?

def get(seq, key):    try:        return seq[key]    except IndexError:        return "not found"print(get([1, 2, 3], 1))print(get({"a": 1}, "z"))
  1. 顯示2後,於KeyError停止
  2. 顯示2與not found這2行
  3. 顯示2與KeyError這2行
  4. 顯示2與None這2行
正確答案A. 顯示2後,於KeyError停止

第1次呼叫是列表的索引1,因此回傳2。第2次是查找字典中不存在的鍵,因此會拋出KeyError,但except中寫的只有IndexError。IndexError與KeyError雖然都是LookupError的子類別,但彼此之間並非父子關係,因此IndexError的except無法捕捉KeyError。未被捕捉的例外會傳播到呼叫端,由於沒有人接住,程式就會停止。若想同時接住兩者,可以寫 except LookupError,或用元組把兩者並列。

第10題 | assert

關於assert陳述式的說明,下列何者正確?

  1. 條件為假時拋出AssertionError,在python3 -O下會被停用
  2. 條件為假時拋出AssertionError,即使在python3 -O下也一定會執行
  3. 條件為真時拋出AssertionError,在python3 -O下會被停用
  4. 條件為假時拋出ValueError,即使在python3 -O下也一定會執行
正確答案A. 條件為假時拋出AssertionError,在python3 -O下會被停用

assert 條件, 訊息 會在條件為假時拋出AssertionError。條件為真時則什麼都不會發生。重要的是,若以像python3 -O這樣的最佳化模式啟動,assert陳述式會整段被移除,1行都不會執行。因此不能把assert用於檢查使用者輸入的目的,那種用途應該用if判斷條件、拋出raise ValueError(...)。最好把assert當作是開發期間用來記下內部前提的工具,把角色區分清楚來記憶。

第11題 | self

關於方法的第1個引數self,下列說明何者正確?

  1. 是為了參照父類別屬性而保留的名稱
  2. 是呼叫該方法之實例本身會自動傳入的引數
  3. 是用來建立實例並回傳給呼叫端的引數
  4. 是類別本身會自動傳入的保留名稱
正確答案B. 是呼叫該方法之實例本身會自動傳入的引數

寫成 d.speak() 時,Python會把它改讀成 Dog.speak(d) 來呼叫。這時候放入第1個引數位置的,就是呼叫端的實例本身。self這個拼寫只是慣例,並非語言保留的名稱。規格只規定第1個引數會接收自身,因此即使自己寫成 Dog.speak(d),也會有相同的動作。建立實例並回傳,是__new__與類別呼叫機制的職責,__init__只是對已經建立好的實例進行初始化。要參照父類別則使用super()。

第12題 | 類別變數

改寫類別變數的下列程式碼,其輸出是哪一項?

class Dog:    kind = "canine"    def __init__(self, name):        self.name = namea = Dog("Rex")b = Dog("Mia")Dog.kind = "wolf"print(a.kind, b.kind)
  1. wolf wolf
  2. canine wolf
  3. canine canine
  4. wolf canine
正確答案A. wolf wolf

kind是直接寫在class之下的類別變數,由該類別所產生的所有實例共用同一個。a與b都沒有在自己的__dict__中持有kind,因此讀取屬性時會回溯到類別一方查看相同的值。對Dog.kind的賦值會改寫這個共用的唯一值,因此已建立的a與b兩者看到的都會是wolf。由於實例建立時不會複製該值,因此不會出現保留canine的形式。

第13題 | 屬性的陰影

對實例一方進行賦值的下列程式碼,其輸出是哪一項?

class Dog:    kind = "canine"    def __init__(self, name):        self.name = namea = Dog("Rex")b = Dog("Mia")a.kind = "husky"print(a.kind, b.kind, Dog.kind)print(sorted(a.__dict__))
  1. husky husky husky 與 ['name']
  2. husky canine canine 與 ['kind', 'name']
  3. husky canine canine 與 ['name']
  4. husky husky husky 與 ['kind', 'name']
正確答案B. husky canine canine 與 ['kind', 'name']

對a.kind賦值並不會改寫類別變數,而是在a的實例字典中建立一個名為kind的新項目。之後讀取a.kind時,會優先找到這個新項目,形成遮蔽類別變數的效果。b與Dog.kind仍維持原本的canine。因此a.__dict__中,除了__init__放入的name外,還會並列kind,用sorted排序後會是['kind', 'name']。全部變成husky的選項,是誤以為對實例賦值會改寫類別變數的情況;只剩['name']的選項,則是誤以為不會產生遮蔽效果時的答案。

第14題 | 被共用

把列表設為類別變數的下列程式碼,其輸出是哪一項?

class Basket:    items = []    def add(self, x):        self.items.append(x)p = Basket()q = Basket()p.add(1)q.add(2)print(p.items, q.items)
  1. p.items是[1],q.items是[1, 2]
  2. p.items是[1, 2],q.items是[2]
  3. p.items是[1, 2],q.items是[1, 2]
  4. p.items是[1],q.items是[2]
正確答案C. p.items是[1, 2],q.items是[1, 2]

self.items.append(x) 並不是對self.items賦值,而只是呼叫透過self.items抵達的那個物件。p與q都沒有各自的items,因此抵達的目的地都是類別變數那個唯一的列表。結果p加入的1與q加入的2都會存進同一個列表,不論從哪一方看都會是[1, 2]。若想讓每個實例各自擁有不同的容器,就要在__init__中以 self.items = [] 賦值建立。這樣一來就會分成[1]與[2]。

第15題 | super()

使用繼承與super()的下列程式碼,其輸出是哪一項?

class Animal:    def __init__(self, name):        self.name = name    def cry(self):        return self.name + " ?"class Cat(Animal):    def __init__(self, name, color):        super().__init__(name)        self.color = color    def cry(self):        return self.name + " meow"c = Cat("Tama", "black")print(c.cry(), c.color, isinstance(c, Animal))
  1. Tama meow None True
  2. Tama ? black True
  3. Tama meow black False
  4. Tama meow black True
正確答案D. Tama meow black True

Cat的__init__呼叫了 super().__init__(name),因此父類別Animal會幫忙設定self.name。之後又把black存入self.color,因此兩個屬性都齊備。cry在子類別中重新定義,因此父類別的實作會被覆蓋,呼叫到的是meow那一個。isinstance(c, Animal)由於Cat繼承自Animal,因此會是True。如果忘記寫super(),name就不會被設定,會發生AttributeError,因此不會像變成None的選項那樣悄悄地失敗。

第16題 | MRO

多重繼承的下列程式碼,其輸出是哪一項?

class A:    def who(self):        return "A"class B(A):    def who(self):        return "B"class C(A):    def who(self):        return "C"class D(B, C):    passprint(D().who())print([k.__name__ for k in D.__mro__])
  1. B 與 ['D', 'B', 'C', 'A', 'object']
  2. C 與 ['D', 'C', 'B', 'A', 'object']
  3. B 與 ['D', 'B', 'A', 'C', 'object']
  4. A 與 ['D', 'B', 'C', 'A', 'object']
正確答案A. B 與 ['D', 'B', 'C', 'A', 'object']

方法解析順序是依C3線性化這項規則決定的,並非單純的深度優先。共通的父類別A,會被放在所有繼承A的子類別之後,因此排列會是D、B、C、A、object。若是鑽進B後就直接走到A的深度優先,會是D、B、A、C、object,但實際上並非如此,這是要留意之處。who()會從MRO開頭開始搜尋,使用第一個找到的,因此會回傳B。若寫成 class E(C, B),就會是E、C、B、A、object,先寫的一方優先。

第17題 | MRO的矛盾

在已定義 class B(A) 的情況下,寫下 class F(A, B): pass 會發生什麼事?

  1. 能夠定義,F().who() 會呼叫到A的版本
  2. 能夠定義,__mro__ 會是 F、A、B、object
  3. 能夠定義,F().who() 會呼叫到B的版本
  4. 在類別定義的時間點就會發生TypeError
正確答案D. 在類別定義的時間點就會發生TypeError

C3線性化有2項規則:要遵守基底類別排列的順序,以及父類別必須排在子類別之後。F(A, B) 是指定A要排在B之前,但B是A的子類別,因此父類別A必須排在子類別B之後。這2項規則無法同時滿足,因此被判定為無法建立一致的MRO,在對class定義求值的時間點就會發生TypeError。這並不是要執行過才會知道,而是在通過class陳述式的那一刻就會失敗。正確寫法應該是 class F(B, A),把子類別寫在前面。

第18題 | 名稱修飾

持有以底線開頭之屬性的下列程式碼,其輸出是哪一項?

class P:    def __init__(self):        self._one = 1        self.__two = 2p = P()print(p._one, p._P__two)print(sorted(p.__dict__))
  1. 1 2 與 ['_one', '_two']
  2. 1 2 與 ['_P__one', '_P__two']
  3. 1 2 與 ['__two', '_one']
  4. 1 2 與 ['_P__two', '_one']
正確答案D. 1 2 與 ['_P__two', '_one']

以1個底線開頭的_one只是慣例上表示內部使用,名稱不會改變,從外部也能正常讀取。以2個底線開頭的__two則會發生名稱修飾,實際的屬性名會被改寫成_P__two,也就是「_類別名__名稱」的形式。因此寫 p.__two 會發生AttributeError,但寫 p._P__two 就能讀取。__dict__中排列的也是修飾後的名稱,因此用sorted排序後會是['_P__two', '_one']。只有以2個底線開頭的才會被修飾,_one不在此列。

第19題 | 特殊方法

定義了特殊方法的下列程式碼,其輸出是哪一項?

class Vec:    def __init__(self, x, y):        self.x = x        self.y = y    def __str__(self):        return "V({}, {})".format(self.x, self.y)    def __repr__(self):        return "Vec(x={}, y={})".format(self.x, self.y)    def __len__(self):        return 2    def __add__(self, o):        return Vec(self.x + o.x, self.y + o.y)v = Vec(1, 2)print(v)print([v])print(len(v), v + Vec(3, 4))
  1. Vec(x=1, y=2) 與 [Vec(x=1, y=2)] 與 2 V(4, 6)
  2. V(1, 2) 與 [Vec(x=1, y=2)] 與 2 V(4, 6)
  3. V(1, 2) 與 [Vec(x=1, y=2)] 與 2 Vec(x=4, y=6)
  4. V(1, 2) 與 [V(1, 2)] 與 2 V(4, 6)
正確答案B. V(1, 2) 與 [Vec(x=1, y=2)] 與 2 V(4, 6)

print(v) 與 str(v) 會使用__str__,因此是V(1, 2)。另一方面,放進列表或字典等容器中顯示時,各個元素會分別使用__repr__,因此是 [Vec(x=1, y=2)]。這種用途的區分正是本題的重點。len(v) 是__len__回傳的2,v + Vec(3, 4) 是__add__回傳的新Vec,把它傳給print又會使用__str__,因此顯示為 V(4, 6)。若類別沒有定義__str__,會以__repr__代替,但反過來__repr__不會被__str__取代。

第20題 | __eq__

只定義了__eq__的下列程式碼,其輸出是哪一項?

class Point:    def __init__(self, x):        self.x = x    def __eq__(self, other):        return self.x == other.xp = Point(1)q = Point(1)print(p == q, p is q)print(Point.__hash__ is None)try:    print(len({p, q}))except TypeError:    print("TypeError")
  1. True False 與 False 與 2
  2. True False 與 True 與 TypeError
  3. True False 與 False 與 1
  4. True True 與 True 與 TypeError
正確答案B. True False 與 True 與 TypeError

由於定義了__eq__,p == q會比較屬性x而得到True,但p與q是分別建立的不同物件,因此p is q仍是False。這裡的重點在於,定義了__eq__的類別,__hash__會自動被設為None,因此Point.__hash__ is None是True。沒有雜湊值的物件無法用作集合的元素或字典的鍵,因此試圖建立{p, q}時會發生TypeError。若也想放進集合,需要自行定義在相等時回傳相同值的__hash__。

第21題 | math

使用math模組的下列程式碼,其輸出是哪一項?

import mathprint(math.sqrt(16))print(math.floor(-1.5), math.ceil(-1.5))
  1. 4.0 與 -2 -1
  2. 4.0 與 -1 -2
  3. 4 與 -2 -1
  4. 4 與 -1 -1
正確答案A. 4.0 與 -2 -1

math.sqrt即使能整除,也會回傳float,因此16的平方根會顯示為4.0而非4。math.floor會往數線下方捨去,因此-1.5會變成-2;math.ceil則往上方,因此-1.5會變成-1。要注意的是,對負數而言,朝0方向捨去的int(-1.5)會是-1,這與floor的結果不同。若把floor與ceil搞混,就會變成-1 -2的排列;若誤以為sqrt回傳值的型別是int,就會得到4。

第22題 | os.path

os.path.splitext('/tmp/z.txt') 與 os.path.basename('/tmp/z.txt') 回傳值的組合是哪一項?

  1. ('/tmp/z', '.txt') 與 'z.txt'
  2. ('/tmp', 'z.txt') 與 '/tmp'
  3. ('/tmp/z', 'txt') 與 'z.txt'
  4. ('/tmp/z.txt', '') 與 'z.txt'
正確答案A. ('/tmp/z', '.txt') 與 'z.txt'

os.path.splitext只會切出副檔名,並在保留句點在副檔名一側的情況下回傳2個元素的元組。因此會是 ('/tmp/z', '.txt')。句點不會被去掉變成'txt'。os.path.basename回傳的是路徑末尾的名稱,因此是'z.txt'。回傳目錄一側'/tmp'的是os.path.dirname,若想同時取得兩者彙整成的元組,要使用os.path.split。splitext是依副檔名而非目錄分隔符來切割,把角色區分清楚來記憶比較好。

第23題 | re的比對

使用re模組的下列程式碼,其輸出是哪一項?

import reprint(re.match("b", "abc"))print(re.search("b", "abc").group())print(re.findall(r"\d+", "a1b22c333"))
  1. b 與 None 與 ['1', '22', '333']
  2. None 與 b 與 ['1', '22', '333']
  3. b 與 b 與 ['1', '22', '333']
  4. None 與 b 與 ['a1', 'b22', 'c333']
正確答案B. None 與 b 與 ['1', '22', '333']

re.match只會從字串開頭比對,'abc'的開頭是'a',與'b'不吻合,因此回傳None。re.search則會在中途搜尋,因此能吻合'b',用.group()就能取出吻合到的字串b。這個差異是re中最常被考的部分。re.findall會回傳把所有吻合該模式的部分蒐集起來的列表,\d+是連續的數字,因此結果是 ['1', '22', '333']。不會連分隔用的字元都包含在內。若沒有吻合,則回傳空列表。

第24題 | datetime

使用datetime的下列程式碼,其輸出是哪一項?

import datetimed = datetime.date(2026, 8, 22)print(d)print(d.strftime("%Y/%m/%d"))print(d + datetime.timedelta(days=10))
  1. 2026/08/22 與 2026/08/22 與 2026/09/01
  2. 2026-08-22 與 2026/08/22 與 2026-09-01
  3. 2026-08-22 與 2026/08/22 與 10 days, 0:00:00
  4. 2026-08-22 與 2026/08/22 與 2026-08-32
正確答案B. 2026-08-22 與 2026/08/22 與 2026-09-01

直接把date物件傳給print,會以ISO格式、連字號分隔顯示成2026-08-22。想改變格式時使用的是strftime,傳入'%Y/%m/%d'就會回傳斜線分隔的字串2026/08/22。對date加上timedelta(days=10),回傳的不是timedelta,而是指向10天後的新date,因此print顯示為2026-09-01。跨月的計算由datetime一方處理妥當,因此不會變成像2026-08-32這樣,只是把日期數字加10而得到的不存在日期。若以為print(d)會與strftime同樣的格式,就會排出斜線分隔的形式,但預設顯示是連字號分隔。日期相減時回傳的不是date而是timedelta,其顯示形式會像10 days, 0:00:00這樣。

第25題 | Counter

使用collections.Counter的下列程式碼,其輸出是哪一項?

from collections import Counterc = Counter("abracadabra")print(c.most_common(2))print(c["a"], c["z"])
  1. [('a', 5), ('b', 2)] 與 5 與 KeyError
  2. [('a', 5), ('r', 2)] 與 5 0
  3. [('a', 5), ('b', 2)] 與 5 0
  4. [('a', 5), ('b', 2), ('r', 2)] 與 5 0
正確答案C. [('a', 5), ('b', 2)] 與 5 0

'abracadabra' 中a有5個,b與r各2個,c與d各1個。most_common(2)會依數量多寡回傳前2名,以元素與個數組成的元組列表,因此是 [('a', 5), ('b', 2)]。數量相同的b與r,先出現者會排在前面,因此採用b。Counter雖是字典的一種,但查詢不存在的元素不會發生KeyError,而是回傳0,這一點與一般字典不同。這樣的設計是為了不必先計數再確認是否存在。

第26題 | namedtuple

使用namedtuple所建立型別的下列程式碼,其輸出是哪一項?

from collections import namedtuplePt = namedtuple("Pt", "x y")p = Pt(1, 2)print(p)print(p.x, p[0], isinstance(p, tuple))
  1. (1, 2) 與 1 1 True
  2. Pt(x=1, y=2) 與 1 1 False
  3. Pt(x=1, y=2) 與 1 1 True
  4. Pt(1, 2) 與 1 1 False
正確答案C. Pt(x=1, y=2) 與 1 1 True

namedtuple所建立的型別,顯示時會呈現帶有型別名稱與欄位名稱的Pt(x=1, y=2)這種形式。值不論用名稱或索引都能讀取,因此p.x與p[0]都會是1。而且namedtuple是元組的衍生型別,因此isinstance(p, tuple)會是True。也就是說它是元組的一種,無法事後改寫元素。若想擁有可改寫的值,可以使用能建立替換值後新實例的_replace,或另外定義一般的類別。

第27題 | 用完即盡

把同一個map物件傳給list()兩次的下列程式碼,其輸出是哪一項?

m = map(str, [1, 2, 3])print(list(m))print(list(m))
  1. 第1行是['1', '2', '3'],第2行是None
  2. 第1行是['1', '2', '3'],第2行是[]
  3. 第1行是[1, 2, 3],第2行是[]
  4. 第1行與第2行都是['1', '2', '3']
正確答案B. 第1行是['1', '2', '3'],第2行是[]

map回傳的不是列表而是迭代器,每次next()只會傳出下一個值,用完就結束了。第1次的list()會把3個都取出,因此第2次的list()就沒有剩餘內容,結果是空列表。它不會出錯也不會變成None,而是悄悄變空,因此不容易察覺。map(str, ...) 會把各元素轉成字串,因此第1次的內容不是數值,而是['1', '2', '3']。同樣的情況在filter、zip、enumerate與生成器上也會發生,若要跑多次,最好在拿到後立刻用list()固定下來。

第28題 | filter與all

使用filter、all、any的下列程式碼,其輸出是哪一項?

print(list(filter(None, [0, 1, "", 2, None, "a"])))print(all([]), any([]))
  1. [1, 2, 'a'] 與 False False
  2. [0, '', None] 與 True False
  3. [1, 2, 'a'] 與 True False
  4. [1, 2, 'a'] 與 False True
正確答案C. [1, 2, 'a'] 與 True False

對filter的第1引數傳入None時,會依元素本身的真假來篩選。0、空字串、None會被判定為假因而被剔除,留下的是[1, 2, 'a']。並不是被剔除的那些留下來。all的規格是只要沒有任何一個假的元素就回傳True,因此在1個元素都沒有的空列表中會是True。any則是只要有1個以上真的元素就是True,因此空的話就是False。這2者在傳入空序列時會回傳相反答案的這一點,很容易被考。

第29題 | 生成器

呼叫含有yield之函式的下列程式碼中,輸出各行的順序是哪一項?

def gen():    print("start")    yield 1    print("mid")    yield 2g = gen()print("created")print(next(g))print(next(g))
  1. 依 start mid created 1 2 的順序
  2. 依 created start mid 1 2 的順序
  3. 依 created start 1 mid 2 的順序
  4. 依 start created 1 mid 2 的順序
正確答案C. 依 created start 1 mid 2 的順序

即使呼叫含有yield的函式,其主體也連1行都不會執行。回傳的只是一個生成器物件,因此created會先顯示。到了第1次的next(g),才終於從開頭開始動作,顯示start之後在yield 1處停下並回傳1。接下來的next(g)會從停下的地方接續,顯示mid之後在yield 2處停下並回傳2。這種延遲執行正是與一般函式決定性的差異,若再呼叫一次next(g),函式就會結束並發生StopIteration。

第30題 | is與==

並列is運算子與==運算子的下列程式碼,其輸出是哪一項?

a = [1, 2]b = [1, 2]c = aprint(a == b, a is b)print(a == c, a is c)
  1. True False 與 True False
  2. True True 與 True True
  3. False False 與 True True
  4. True False 與 True True
正確答案D. True False 與 True True

==是詢問值是否相等的運算子,由於a與b內容相同,因此是True。is則是詢問是否為同一個物件的運算子,a與b是分別建立的2個列表,因此是False。另一方面,c = a並沒有建立新列表,只是為同一個物件另取一個別名,因此a is c會是True。也就是說,透過c改寫該列表,從a來看也會看到變化。None是只存在1個的特殊物件,因此寫成 x is None 才是正確做法,但若想比較值,則應一律使用==。

練習:作答本頁的題目

這是隨機出題的練習工具(在啟用 JavaScript 時運作)。即使不使用此工具,也能閱讀上方的所有題目與解說。

※ 解說是供學習用的資訊。考試的出題範圍與制度每年可能變動,請務必確認主辦機構的官方公告。

本頁面譯自日文原文。若譯文與原文內容不一致,以日文版為準。 查看日文原文