Khi thực thi đoạn mã sau, các dòng được hiển thị theo thứ tự từ trên xuống là gì?
def calc(a, b): try: print("try") r = a / b except ZeroDivisionError: print("except") else: print("else") finally: print("finally")calc(9, 3)
try, else, except
try, else, finally
try, except, else
try, except, finally
Đáp ánB. try, else, finally
9 / 3 không gây ra ngoại lệ nên không đi vào except. Mệnh đề chỉ được thực thi khi không xảy ra ngoại lệ là else, nên sau try thì else được thực thi. Mệnh đề finally luôn được thực thi bất kể có ngoại lệ hay không, nên chạy sau cùng. Do đó có 3 dòng try, else, finally. Các lựa chọn có xuất hiện except là do hiểu nhầm rằng đã xảy ra ngoại lệ, nhưng lệnh gọi này không phát sinh ZeroDivisionError. Lựa chọn thiếu finally là do hiểu nhầm finally là "phần xử lý dọn dẹp khi có ngoại lệ xảy ra".
Câu 2 | Mệnh đề else
Đâu là điều kiện đúng để mệnh đề else của câu lệnh try được thực thi?
Luôn được thực thi sau cùng bất kể có phát sinh ngoại lệ hay không
Chỉ được thực thi khi mệnh đề try phát sinh ngoại lệ
Được thực thi khi không khớp với bất kỳ mệnh đề except nào
Chỉ được thực thi khi mệnh đề try không phát sinh ngoại lệ
Đáp ánD. Chỉ được thực thi khi mệnh đề try không phát sinh ngoại lệ
Mệnh đề else của câu lệnh try chỉ được thực thi khi mệnh đề try kết thúc trót lọt đến cuối mà không phát sinh ngoại lệ. Nên nhớ rằng nó có hướng ngược với else của câu lệnh if (vốn dành cho "khi điều kiện sai"): else của try lại đứng về phía "khi thành công". Mệnh đề được thực thi khi phát sinh ngoại lệ là except. Mệnh đề luôn được thực thi bất kể có ngoại lệ hay không là finally. Trường hợp không khớp với bất kỳ except nào thì không rơi vào else, mà ngoại lệ đó sẽ được truyền tiếp đến phía gọi.
f() chuẩn bị sẵn giá trị để return, nhưng trước khi thực sự trả về thì mệnh đề finally được thực thi. Vì vậy F được hiển thị trước, còn giá trị trả về vẫn là T. g() có return nằm trong mệnh đề finally, và điều này ghi đè lên return của mệnh đề try, nên giá trị trả về là N. print đánh giá f() và g() rồi mới gộp hiển thị trên 1 dòng, nên dòng F đứng trước, dòng T N đứng sau. Lựa chọn ra T T là khi nghĩ return trong finally bị bỏ qua; lựa chọn F đứng sau là khi nghĩ finally chạy sau return.
Câu 4 | Ngoại lệ không bắt được
Đâu là ngoại lệ không thể bắt được bởi mệnh đề except viết là except Exception?
FileNotFoundError
ZeroDivisionError
KeyboardInterrupt
ModuleNotFoundError
Đáp ánC. KeyboardInterrupt
KeyboardInterrupt nằm ngay dưới BaseException, không kế thừa từ Exception. issubclass(KeyboardInterrupt, Exception) là False. Vì đây là ngoại lệ phát sinh khi người dùng cố dừng chương trình bằng Ctrl-C, nên phân cấp được tách riêng để tránh bị except Exception viết rộng vô tình nuốt mất. SystemExit cũng nằm ở cùng vị trí đó. Ba cái còn lại đều là hậu duệ của Exception: ZeroDivisionError nằm dưới ArithmeticError, FileNotFoundError nằm dưới OSError, ModuleNotFoundError nằm dưới ImportError, nên đều có thể bắt được bằng except Exception.
Câu 5 | Lớp cha của ngoại lệ
Lớp cha gần nhất chung của IndexError và KeyError là gì?
ReferenceError
ArithmeticError
AttributeError
LookupError
Đáp ánD. LookupError
Cả IndexError lẫn KeyError đều mang cùng bản chất là "tra bằng chỉ số hay khóa nhưng không tìm thấy", nên được đặt dưới một lớp cha chung là LookupError. Nếu viết except LookupError, có thể bắt gộp cả việc truy cập ngoài phạm vi của danh sách lẫn khóa không tồn tại của từ điển. ArithmeticError là lớp cha của ZeroDivisionError và các ngoại lệ tương tự, dùng cho trường hợp bản thân phép tính không thể thực hiện được. AttributeError là ngoại lệ khi không tìm thấy thuộc tính, ReferenceError là ngoại lệ khi đối tượng mà tham chiếu yếu (weak reference) trỏ tới đã biến mất; cả hai đều nằm ngay dưới Exception, thuộc một dòng khác với LookupError.
Câu 6 | Thứ tự của except
Đâu là kết quả hiển thị đúng khi thực thi đoạn mã sau?
try: x = 8 / 0except ArithmeticError: print("A")except ZeroDivisionError: print("Z")
Không hiển thị gì và dừng lại
Chỉ hiển thị Z
Hiển thị cả A và Z
Chỉ hiển thị A
Đáp ánD. Chỉ hiển thị A
8 / 0 sẽ ném ra ZeroDivisionError, nhưng ZeroDivisionError là lớp con của ArithmeticError. Các mệnh đề except được đối chiếu tuần tự từ trên xuống, và dừng lại ngay khi khớp lần đầu tiên, nên except ArithmeticError viết trước sẽ bắt lấy nó, chỉ A được hiển thị. except ZeroDivisionError viết sau không có cơ hội chạy. Vì chỉ có đúng 1 mệnh đề except khớp được thực thi, nên không thể có cả A lẫn Z cùng hiển thị. Nguyên tắc là xếp loại hẹp (lớp con) trước, loại rộng (lớp cha) sau, nhưng đoạn mã trong câu hỏi này đã đảo ngược thứ tự đó.
Nếu viết một tuple bao trong dấu ngoặc tròn sau except, nó sẽ được thực thi khi khớp với bất kỳ cái nào trong đó. conv("21") vì int("21") ra 21 nên trả về 42. conv("abc") tuy có thể truyền như chuỗi nhưng không thể diễn giải thành số nên gây ra ValueError. conv(None) không phải kiểu có thể truyền cho int() nên gây ra TypeError. Sự phân biệt "kiểu đúng nhưng giá trị không phù hợp thì là ValueError, còn nếu ngay từ đầu kiểu không khớp thì là TypeError" chính là thứ tự của đáp án.
Câu 8 | Ném lại ngoại lệ
Kết quả hiển thị của đoạn mã sau, dùng ngoại lệ tự định nghĩa và raise không có đối số, là gì?
class LoadError(Exception): passdef load(): try: raise LoadError("bad") except LoadError: print("inner") raisetry: load()except Exception as e: print("outer", type(e).__name__)
Chỉ có inner, outer không được hiển thị
2 dòng inner và outer Exception
2 dòng inner và outer LoadError
inner rồi dừng lại tại LoadError
Đáp ánC. 2 dòng inner và outer LoadError
Nếu chỉ viết raise mà không kèm đối số bên trong except, ngoại lệ đang được xử lý sẽ được ném lại nguyên vẹn cho phía gọi. Do đó, sau khi hiển thị inner, LoadError được truyền ra ngoài load() và except Exception ở bên ngoài sẽ nhận lấy nó. Vì LoadError kế thừa từ Exception nên có thể bắt được bằng except Exception, và không dừng lại. type(e).__name__ trả về tên của chính lớp ngoại lệ đã được ném ra, nên hiển thị ra là LoadError chứ không phải tên kiểu Exception phía bắt được. Kết quả chỉ có inner là khi ngoại lệ bị nuốt tại chỗ mà không ném lại.
Lệnh gọi thứ nhất dùng chỉ số 1 của danh sách nên trả về 2. Lệnh gọi thứ hai tra một khóa không có trong từ điển nên phát sinh KeyError, nhưng trong except chỉ viết IndexError. Dù IndexError và KeyError đều là con của LookupError, nhưng chúng không phải quan hệ cha con với nhau, nên except của IndexError không thể bắt được KeyError. Ngoại lệ không được bắt sẽ truyền tới phía gọi, và vì không có ai nhận lấy nên chương trình dừng lại. Nếu muốn nhận cả hai thì cần viết except LookupError, hoặc xếp cả hai trong một tuple.
Câu 10 | assert
Đâu là mô tả đúng về câu lệnh assert?
Nếu điều kiện sai thì ném ra AssertionError, và bị vô hiệu hóa khi chạy python3 -O
Nếu điều kiện sai thì ném ra AssertionError, và vẫn luôn được thực thi kể cả với python3 -O
Nếu điều kiện đúng thì ném ra AssertionError, và bị vô hiệu hóa khi chạy python3 -O
Nếu điều kiện sai thì ném ra ValueError, và vẫn luôn được thực thi kể cả với python3 -O
Đáp ánA. Nếu điều kiện sai thì ném ra AssertionError, và bị vô hiệu hóa khi chạy python3 -O
assert điều kiện, thông báo sẽ ném ra AssertionError khi điều kiện là sai. Khi điều kiện đúng thì không có gì xảy ra. Điều quan trọng là khi khởi động ở chế độ tối ưu hóa như python3 -O, toàn bộ câu lệnh assert sẽ bị loại bỏ, không có dòng nào được thực thi. Do đó không được dùng assert với mục đích kiểm tra đầu vào của người dùng; cho mục đích đó cần dùng if để xét điều kiện rồi ném raise ValueError(...). Nên nhớ vai trò của assert là công cụ để ghi lại các tiền đề nội bộ trong quá trình phát triển.
Câu 11 | self
Đâu là mô tả đúng về đối số đầu tiên self của một phương thức?
Là một tên dành riêng để tham chiếu đến thuộc tính của lớp cha
Là một đối số được tự động truyền chính bản thể đã gọi vào
Là đối số dùng để tạo bản thể rồi trả về cho phía gọi
Là một tên dành riêng, được tự động truyền chính bản thân lớp vào
Đáp ánB. Là một đối số được tự động truyền chính bản thể đã gọi vào
Khi viết d.speak(), Python sẽ hiểu lại thành lệnh gọi Dog.speak(d). Lúc này, thứ được đặt vào vị trí đối số đầu tiên chính là bản thể đã gọi. Cách đặt tên self chỉ là quy ước, không phải tên do ngôn ngữ dành riêng. Vì chỉ có vị trí đối số đầu tiên nhận chính bản thân là được quy định, nên nếu tự viết Dog.speak(d) thì cũng hoạt động giống hệt. Việc tạo và trả về bản thể là vai trò của __new__ và cơ chế gọi lớp, còn __init__ chỉ khởi tạo cho bản thể đã được tạo sẵn. Muốn tham chiếu đến lớp cha thì dùng super().
Câu 12 | Biến lớp
Kết quả hiển thị của đoạn mã sau, ghi đè một biến lớp (class variable), là gì?
kind là một biến lớp được viết ngay dưới class, mọi bản thể được tạo từ lớp đó đều dùng chung đúng một biến này. Cả a lẫn b đều không có kind trong __dict__ của riêng mình, nên khi đọc thuộc tính sẽ phải truy ngược lên phía lớp và thấy cùng một giá trị. Việc gán vào Dog.kind sẽ ghi đè đúng biến dùng chung đó, nên cả a lẫn b đã được tạo từ trước đều thấy wolf. Vì giá trị không được sao chép riêng tại thời điểm tạo bản thể, nên không thể ra dạng vẫn còn giữ canine.
Câu 13 | Cái bóng của thuộc tính
Kết quả hiển thị của đoạn mã sau, gán vào phía bản thể, là gì?
Việc gán vào a.kind không ghi đè biến lớp, mà tạo ra một mục mới tên là kind trong từ điển bản thể của a. Từ đó về sau, khi đọc a.kind thì mục mới này được tìm thấy trước, tạo thành một cái bóng phủ lên biến lớp. b và Dog.kind vẫn giữ nguyên canine như cũ. Do đó a.__dict__ ngoài name đã đưa vào lúc __init__ còn có thêm kind, khi sorted sẽ ra ['kind', 'name']. Lựa chọn toàn bộ đều là husky là do hiểu nhầm rằng việc gán vào bản thể sẽ ghi đè biến lớp; lựa chọn chỉ có ['name'] là do nghĩ rằng không tạo ra cái bóng nào cả.
Câu 14 | Được dùng chung
Kết quả hiển thị của đoạn mã sau, đặt một danh sách làm biến lớp, là gì?
self.items.append(x) không phải là gán vào self.items, mà chỉ là gọi phương thức trên đối tượng đã tìm được thông qua self.items. Vì cả p lẫn q đều không có items của riêng mình, nên nơi tìm đến đều là cùng một danh sách duy nhất của biến lớp. Kết quả là số 1 được thêm từ p và số 2 được thêm từ q đều vào cùng một danh sách đó, nên dù xem từ bên nào cũng thấy [1, 2]. Nếu muốn mỗi bản thể có một vật chứa riêng, cần gán tạo mới bằng self.items = [] bên trong __init__. Khi đó sẽ tách thành [1] và [2].
Câu 15 | super()
Kết quả hiển thị của đoạn mã sau, dùng kế thừa và super(), là gì?
__init__ của Cat gọi super().__init__(name), nên lớp cha Animal sẽ thiết lập self.name giúp. Sau đó self.color được gán black, nên cả hai thuộc tính đều đầy đủ. cry được định nghĩa lại ở lớp con nên phần triển khai của lớp cha bị ghi đè, meow sẽ được gọi. isinstance(c, Animal) sẽ là True vì Cat đã kế thừa từ Animal. Nếu quên viết super() thì name sẽ không được thiết lập và gây ra AttributeError, nên sẽ không âm thầm thất bại thành dạng None như trong một lựa chọn.
Câu 16 | MRO
Kết quả hiển thị của đoạn mã sau, dùng đa kế thừa (multiple inheritance), là gì?
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__])
B và ['D', 'B', 'C', 'A', 'object']
C và ['D', 'C', 'B', 'A', 'object']
B và ['D', 'B', 'A', 'C', 'object']
A và ['D', 'B', 'C', 'A', 'object']
Đáp ánA. B và ['D', 'B', 'C', 'A', 'object']
Thứ tự giải quyết phương thức (MRO) được quyết định theo quy tắc tuyến tính hóa C3, không đơn giản là ưu tiên chiều sâu. Lớp cha chung A sẽ được đặt sau tất cả các lớp con nào kế thừa từ A, nên thứ tự trở thành D, B, C, A, object. Nếu theo kiểu ưu tiên chiều sâu, tức là hễ đi vào B thì đi thẳng đến A, sẽ ra D, B, A, C, object, nhưng thực tế không như vậy, đây là điểm cần lưu ý. who() sẽ tìm từ đầu MRO và dùng cái đầu tiên tìm thấy, nên B được trả về. Nếu viết class E(C, B) thì sẽ ra E, C, B, A, object; cái viết trước sẽ được ưu tiên.
Câu 17 | Mâu thuẫn MRO
Khi class B(A) đã được định nghĩa từ trước, nếu viết class F(A, B): pass thì điều gì sẽ xảy ra?
Định nghĩa được, F().who() sẽ gọi cái của A
Định nghĩa được, __mro__ sẽ là F, A, B, object
Định nghĩa được, F().who() sẽ gọi cái của B
Xảy ra TypeError ngay tại thời điểm định nghĩa lớp
Đáp ánD. Xảy ra TypeError ngay tại thời điểm định nghĩa lớp
Tuyến tính hóa C3 có 2 quy tắc: phải giữ đúng thứ tự đã liệt kê các lớp cơ sở, và lớp cha phải đứng sau lớp con. F(A, B) là chỉ định đặt A trước B, nhưng vì B là lớp con của A nên lớp cha A bắt buộc phải đứng sau lớp con B. Vì hai điều kiện này không thể thỏa mãn đồng thời, nên không thể tạo ra một MRO nhất quán, và ngay tại thời điểm đánh giá định nghĩa lớp sẽ xảy ra TypeError. Đây không phải là điều chỉ biết được khi chạy thử, mà thất bại ngay khoảnh khắc câu lệnh class được xử lý qua. Cách viết đúng là class F(B, A), đặt lớp con lên trước.
Câu 18 | Name mangling
Kết quả hiển thị của đoạn mã sau, có thuộc tính bắt đầu bằng dấu gạch dưới, là gì?
_one bắt đầu bằng 1 dấu gạch dưới chỉ mang tính quy ước để chỉ ra rằng đây là dùng nội bộ, tên không hề bị thay đổi và vẫn đọc được bình thường từ bên ngoài. __two bắt đầu bằng 2 dấu gạch dưới thì bị áp dụng cơ chế đổi tên (name mangling), tên thuộc tính thực tế bị viết lại thành _P__two, tức là dạng _tên lớp__tên. Vì vậy nếu viết p.__two sẽ gây ra AttributeError, nhưng nếu viết p._P__two thì đọc được. Tên xếp trong __dict__ cũng là tên sau khi đã đổi, nên khi sorted sẽ ra ['_P__two', '_one']. Chỉ những tên bắt đầu bằng 2 dấu gạch dưới mới bị đổi tên, _one không thuộc diện đó.
Câu 19 | Phương thức đặc biệt
Kết quả hiển thị của đoạn mã sau, định nghĩa các phương thức đặc biệt, là gì?
print(v) và str(v) dùng __str__ nên ra V(1, 2). Trong khi đó, khi hiển thị bên trong một vật chứa như danh sách hay từ điển, __repr__ được dùng cho từng phần tử, nên ra [Vec(x=1, y=2)]. Sự phân biệt cách dùng này chính là trọng tâm của câu hỏi. len(v) là giá trị mà __len__ trả về, tức 2; v + Vec(3, 4) là một Vec mới do __add__ trả về, và khi truyền nó cho print thì lại dùng __str__ nên hiển thị ra V(4, 6). Với lớp không định nghĩa __str__ thì __repr__ sẽ được dùng thay, nhưng ngược lại thì __str__ không bao giờ thay thế cho __repr__.
Câu 20 | __eq__
Kết quả hiển thị của đoạn mã sau, chỉ định nghĩa __eq__, là gì?
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")
True False và False và 2
True False và True và TypeError
True False và False và 1
True True và True và TypeError
Đáp ánB. True False và True và TypeError
Vì đã định nghĩa __eq__ nên p == q so sánh thuộc tính x và cho ra True, nhưng vì p và q là 2 đối tượng được tạo riêng biệt nên p is q vẫn là False. Điều quan trọng ở đây là lớp nào định nghĩa __eq__ thì __hash__ sẽ tự động được đặt thành None, nên Point.__hash__ is None là True. Vì đối tượng không có hash không thể dùng làm phần tử tập hợp hay khóa từ điển, nên cố tạo {p, q} sẽ gây ra TypeError. Nếu muốn cũng đưa được vào tập hợp, cần tự định nghĩa __hash__ sao cho trả về cùng một giá trị khi bằng nhau.
Câu 21 | math
Kết quả hiển thị của đoạn mã sau, dùng mô-đun math, là gì?
math.sqrt luôn trả về float kể cả khi chia hết, nên căn bậc hai của 16 hiển thị là 4.0 chứ không phải 4. math.floor làm tròn xuống theo hướng nhỏ hơn trên trục số nên -1.5 thành -2, còn math.ceil làm tròn lên nên -1.5 thành -1. Cần lưu ý rằng với số âm, int(-1.5) làm tròn về phía 0 sẽ cho -1, khác với kết quả của floor. Nếu nhầm lẫn floor với ceil sẽ ra thứ tự -1 -2; nếu nghĩ nhầm kiểu trả về của sqrt là int thì sẽ ra 4.
Câu 22 | os.path
Tổ hợp giá trị mà os.path.splitext('/tmp/z.txt') và os.path.basename('/tmp/z.txt') trả về là gì?
('/tmp/z', '.txt') và 'z.txt'
('/tmp', 'z.txt') và '/tmp'
('/tmp/z', 'txt') và 'z.txt'
('/tmp/z.txt', '') và 'z.txt'
Đáp ánA. ('/tmp/z', '.txt') và 'z.txt'
os.path.splitext tách riêng phần đuôi mở rộng (extension), giữ nguyên dấu chấm ở phía đuôi mở rộng và trả về một tuple 2 phần tử. Do đó kết quả là ('/tmp/z', '.txt'). Không có chuyện dấu chấm bị mất đi thành 'txt'. os.path.basename trả về tên ở cuối đường dẫn nên là 'z.txt'. Trả về phần thư mục '/tmp' là vai trò của os.path.dirname; khi muốn có cả hai gộp trong một tuple thì dùng os.path.split. Nên phân biệt và ghi nhớ rằng splitext tách theo đuôi mở rộng chứ không phải theo dấu phân cách thư mục.
Câu 23 | Đối chiếu của re
Kết quả hiển thị của đoạn mã sau, dùng mô-đun re, là gì?
re.match chỉ đối chiếu từ đầu chuỗi, nên ký tự đầu của 'abc' là 'a', không khớp với 'b' nên trả về None. re.search tìm cả ở giữa nên khớp với 'b', và .group() lấy ra được chuỗi đã khớp là b. Sự khác biệt này chính là điểm hay được hỏi nhất về re. re.findall trả về một danh sách gồm tất cả các phần đã khớp với mẫu, và \d+ nghĩa là các chữ số liên tiếp nên ra ['1', '22', '333']. Không có chuyện các ký tự phân cách cũng bị gộp vào. Nếu không khớp gì thì trả về danh sách rỗng.
Câu 24 | datetime
Kết quả hiển thị của đoạn mã sau, dùng datetime, là gì?
Khi print trực tiếp một đối tượng date, nó sẽ hiển thị theo định dạng ISO phân cách bằng dấu gạch ngang, thành 2026-08-22. Khi muốn đổi định dạng thì dùng strftime; nếu truyền '%Y/%m/%d' sẽ trả về chuỗi phân cách bằng dấu gạch chéo là 2026/08/22. Khi cộng timedelta(days=10) vào date, cái trả về không phải là timedelta mà là một date mới trỏ đến 10 ngày sau, nên khi print sẽ hiển thị 2026-09-01. Việc tính toán vượt qua ranh giới tháng được phía datetime lo liệu, nên sẽ không ra một ngày không tồn tại kiểu chỉ cộng thêm 10 vào số ngày như 2026-08-32. Nếu nghĩ print(d) cũng theo cùng định dạng với strftime thì sẽ ra dạng phân cách bằng dấu gạch chéo, nhưng cách hiển thị mặc định là phân cách bằng dấu gạch ngang. Khi trừ hai ngày cho nhau thì cái trả về là timedelta chứ không phải date, và cách hiển thị của nó có dạng như 10 days, 0:00:00.
Câu 25 | Counter
Kết quả hiển thị của đoạn mã sau, dùng collections.Counter, là gì?
from collections import Counterc = Counter("abracadabra")print(c.most_common(2))print(c["a"], c["z"])
[('a', 5), ('b', 2)] và 5 và KeyError
[('a', 5), ('r', 2)] và 5 0
[('a', 5), ('b', 2)] và 5 0
[('a', 5), ('b', 2), ('r', 2)] và 5 0
Đáp ánC. [('a', 5), ('b', 2)] và 5 0
'abracadabra' có 5 chữ a, 2 chữ b, 2 chữ r, 1 chữ c và 1 chữ d. most_common(2) chỉ trả về 2 mục nhiều nhất theo thứ tự giảm dần, dưới dạng danh sách các tuple (phần tử, số lượng), nên ra [('a', 5), ('b', 2)]. Giữa b và r có cùng số lượng thì cái xuất hiện trước sẽ được xếp trước, nên b được chọn. Counter là một dạng từ điển, nhưng khác với từ điển thông thường ở chỗ tra một phần tử không có trong đó sẽ không gây ra KeyError mà trả về 0. Đây là thiết kế để không cần phải kiểm tra sự tồn tại trước khi đếm.
Câu 26 | namedtuple
Kết quả hiển thị của đoạn mã sau, dùng kiểu được tạo bởi namedtuple, là gì?
Kiểu do namedtuple tạo ra, khi hiển thị sẽ có dạng Pt(x=1, y=2) kèm tên kiểu và tên trường. Giá trị có thể đọc bằng tên hoặc bằng chỉ số, nên cả p.x lẫn p[0] đều là 1. Và vì namedtuple là kiểu phái sinh từ tuple, nên isinstance(p, tuple) là True. Nói cách khác đây là một dạng của tuple, không thể ghi đè phần tử về sau. Nếu muốn có giá trị có thể thay đổi được, cần dùng _replace để tạo một bản thể mới đã thay giá trị, hoặc định nghĩa một lớp thông thường.
Câu 27 | Dùng một lần là hết
Kết quả hiển thị của đoạn mã sau, truyền cùng một đối tượng map cho list() hai lần, là gì?
m = map(str, [1, 2, 3])print(list(m))print(list(m))
Dòng 1 là ['1', '2', '3'], dòng 2 là None
Dòng 1 là ['1', '2', '3'], dòng 2 là []
Dòng 1 là [1, 2, 3], dòng 2 là []
Cả dòng 1 lẫn dòng 2 đều là ['1', '2', '3']
Đáp ánB. Dòng 1 là ['1', '2', '3'], dòng 2 là []
Cái mà map trả về không phải là danh sách mà là một iterator, mỗi lần gọi next() sẽ đưa ra giá trị tiếp theo, và khi hết thì không còn gì nữa. Lần list() đầu tiên đã lấy hết cả 3 giá trị, nên lần list() thứ hai trở thành một danh sách rỗng vì không còn gì sót lại. Không gây lỗi, cũng không thành None, mà âm thầm trở thành rỗng nên rất dễ bị bỏ sót. map(str, ...) chuyển từng phần tử thành chuỗi, nên nội dung lần đầu không phải là số mà là ['1', '2', '3']. Điều tương tự cũng xảy ra với filter, zip, enumerate và generator, nên nếu cần duyệt nhiều lần thì nên chốt lại bằng list() ngay sau khi nhận được.
Câu 28 | filter và all
Kết quả hiển thị của đoạn mã sau, dùng filter, all và any, là gì?
Khi truyền None làm đối số thứ nhất của filter, việc lọc dựa vào chính giá trị đúng-sai của phần tử đó. Vì 0, chuỗi rỗng và None đều được đánh giá là sai (falsy) nên bị loại, còn lại là [1, 2, 'a']. Không phải là những cái bị loại còn ở lại. all được quy định là trả về True nếu không có phần tử nào là sai, nên với một danh sách rỗng hoàn toàn không có phần tử nào thì cũng là True. any thì trả về True nếu có ít nhất 1 phần tử đúng, nên nếu rỗng thì là False. Việc 2 hàm này trả về câu trả lời ngược nhau khi truyền vào một dãy rỗng chính là điểm hay được hỏi.
Câu 29 | Generator
Về đoạn mã sau, gọi một hàm có chứa yield, thứ tự các dòng được hiển thị là gì?
Dù gọi một hàm có chứa yield, thân hàm cũng không chạy một dòng nào cả. Vì cái được trả về chỉ là một đối tượng generator, nên created sẽ được hiển thị trước. Chỉ đến lần gọi next(g) đầu tiên thì mới thực sự bắt đầu chạy từ đầu, hiển thị start rồi dừng lại ở yield 1 và trả về 1. Lần next(g) tiếp theo tiếp tục chạy từ chỗ đã dừng, hiển thị mid rồi dừng lại ở yield 2 và trả về 2. Việc thực thi trì hoãn này chính là khác biệt quyết định so với hàm thông thường; nếu gọi thêm next(g) nữa thì hàm sẽ kết thúc và gây ra StopIteration.
Câu 30 | is và ==
Kết quả hiển thị của đoạn mã sau, xếp toán tử is và toán tử ==, là gì?
a = [1, 2]b = [1, 2]c = aprint(a == b, a is b)print(a == c, a is c)
True False và True False
True True và True True
False False và True True
True False và True True
Đáp ánD. True False và True True
== là toán tử hỏi giá trị có bằng nhau hay không, nên với a và b có cùng nội dung sẽ là True. is là toán tử hỏi có phải cùng một đối tượng hay không; a và b là 2 danh sách được tạo riêng biệt nên là False. Trong khi đó c = a không tạo danh sách mới mà chỉ đặt thêm một tên khác cho cùng một đối tượng, nên a is c là True. Nói cách khác, nếu ghi đè danh sách thông qua c thì từ a cũng thấy được sự thay đổi đó. None là một đối tượng đặc biệt chỉ tồn tại duy nhất một cái, nên cách viết chuẩn mực là x is None, nhưng khi muốn so sánh giá trị thì luôn dùng ==.
Luyện tập: giải các câu hỏi trên trang này
Đây là công cụ luyện tập với câu hỏi ngẫu nhiên (hoạt động khi JavaScript được bật). Bạn vẫn có thể đọc toàn bộ câu hỏi và phần giải thích ở trên.
* Lời giải là thông tin phục vụ việc học. Phạm vi ra đề và chế độ thi có thể thay đổi theo từng năm, vì vậy hãy luôn kiểm tra thông báo chính thức của đơn vị tổ chức thi.
Trang này là bản dịch từ nguyên bản tiếng Nhật. Nếu nội dung bản dịch và nguyên bản khác nhau, bản tiếng Nhật sẽ được ưu tiên. Xem nguyên bản tiếng Nhật