Sala de studiu Karinoya

Calificări · Laboratorul de promovare Cloud / AI / Python

Excepții, clase și biblioteca standard

Puteți citi întrebările și explicațiile în Română. Lecțiile (articolele explicative) sunt disponibile doar în japoneză.

Vedeți versiunea în japoneză (cu lecții) →

Î1 | Ordinea într-un try

La rularea următorului cod, care este ordinea, de sus în jos, a liniilor afișate?

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
Răspuns corectB. try, else, finally

9 / 3 nu produce nicio excepție, deci nu se intră în except. Clauza else se execută doar atunci când nu a apărut nicio excepție, deci, după try, rulează else. Clauza finally rulează întotdeauna, indiferent de existența unei excepții, deci se execută ultima. Prin urmare, rezultă cele 3 linii: try, else, finally. Variantele care includ except corespund ideii greșite că a apărut o excepție, dar la acest apel nu se produce ZeroDivisionError. Varianta din care lipsește finally corespunde interpretării greșite a lui finally ca „operațiune de curățenie doar atunci când apare o excepție”.

Î2 | Clauza else

Care este condiția corectă în care se execută clauza else a unei instrucțiuni try?

  1. Se execută întotdeauna ultima, indiferent de apariția unei excepții
  2. Se execută doar atunci când a apărut o excepție în clauza try
  3. Se execută atunci când nu s-a potrivit niciun except
  4. Se execută doar atunci când nu a apărut nicio excepție în clauza try
Răspuns corectD. Se execută doar atunci când nu a apărut nicio excepție în clauza try

Clauza else a unei instrucțiuni try se execută doar atunci când clauza try s-a încheiat până la capăt, fără nicio excepție. Spre deosebire de else al instrucțiunii if, care se leagă de „condiția este falsă”, else al lui try se leagă de „a mers bine” — direcțiile fiind opuse, e util să fie reținut astfel. Ceea ce se execută atunci când apare o excepție este clauza except. Ceea ce se execută întotdeauna, indiferent de excepție, este clauza finally. Dacă nu se potrivește niciun except, execuția nu trece la else, ci excepția respectivă se propagă mai departe, către apelant.

Î3 | Prioritatea lui finally

Care este rezultatul la rularea codului următor?

def f():    try:        return "T"    finally:        print("F")def g():    try:        return "T"    finally:        return "N"print(f(), g())
  1. Se afișează, pe 2 linii, F și T T
  2. Se afișează, pe 2 linii, F și T N
  3. Se afișează, pe 2 linii, T T și F
  4. Se afișează, pe 2 linii, T N și F
Răspuns corectB. Se afișează, pe 2 linii, F și T N

f() pregătește valoarea de returnat, dar înainte de a o returna efectiv execută clauza finally. De aceea, F se afișează primul, iar valoarea returnată rămâne T. g() are un return chiar în clauza finally, iar acesta suprascrie return-ul din clauza try, deci valoarea returnată devine N. print evaluează f() și g() înainte să afișeze pe un singur rând, deci rândul cu F apare primul, iar rândul cu T N urmează. Varianta T T ar corespunde ideii că return-ul din finally este ignorat, iar varianta cu F pe rândul secund ar corespunde ideii că finally rulează după return.

Î4 | Excepția necaptabilă

Care excepție nu poate fi captată de o clauză except Exception?

  1. FileNotFoundError
  2. ZeroDivisionError
  3. KeyboardInterrupt
  4. ModuleNotFoundError
Răspuns corectC. KeyboardInterrupt

KeyboardInterrupt se află direct sub BaseException și nu moștenește din Exception. issubclass(KeyboardInterrupt, Exception) este False. Fiind provocată atunci când utilizatorul încearcă să oprească programul cu Ctrl-C, ierarhia este separată tocmai pentru a nu fi accidental înghițită de un except Exception scris larg. SystemExit se află în aceeași poziție. Celelalte trei sunt toate descendente ale lui Exception: ZeroDivisionError sub ArithmeticError, FileNotFoundError sub OSError, iar ModuleNotFoundError sub ImportError, deci toate pot fi captate cu except Exception.

Î5 | Clasa-părinte comună a excepțiilor

Care este clasa-părinte imediată comună lui IndexError și KeyError?

  1. ReferenceError
  2. ArithmeticError
  3. AttributeError
  4. LookupError
Răspuns corectD. LookupError

Atât IndexError, cât și KeyError, au aceeași natură — o căutare după index sau cheie care nu a găsit nimic — motiv pentru care sunt plasate sub o clasă-părinte comună, LookupError. Scriind except LookupError, se pot prinde deodată atât un acces în afara intervalului unei liste, cât și o cheie inexistentă a unui dicționar. ArithmeticError este părintele lui ZeroDivisionError și altele similare, pentru situații în care calculul în sine nu poate avea loc. AttributeError apare când un atribut nu este găsit, iar ReferenceError atunci când ținta unei referințe slabe a dispărut — ambele se află direct sub Exception și aparțin unei alte familii decât LookupError.

Î6 | Ordinea except

Care este rezultatul corect la rularea următorului cod?

try:    x = 8 / 0except ArithmeticError:    print("A")except ZeroDivisionError:    print("Z")
  1. Nu se afișează nimic, iar execuția se oprește
  2. Se afișează doar Z
  3. Se afișează A și Z
  4. Se afișează doar A
Răspuns corectD. Se afișează doar A

8 / 0 produce ZeroDivisionError, dar ZeroDivisionError este o subclasă a lui ArithmeticError. Clauzele except sunt verificate în ordine, de sus în jos, iar la prima potrivire căutarea se oprește, deci except ArithmeticError, scris primul, prinde excepția, iar se afișează doar A. Clauza except ZeroDivisionError, scrisă mai jos, nu mai apucă să se execute. Doar o singură clauză except se execută la o potrivire, deci nu pot apărea ambele, A și Z. Principiul este ca cele mai specifice (subclasele) să fie scrise primele, iar cele mai generale (clasele-părinte) după — codul din această întrebare are ordinea inversată.

Î7 | Excepții multiple

Care este singura linie afișată la rularea următorului cod?

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
Răspuns corectA. 42 ValueError TypeError

Dacă după except se scrie un tuplu, între paranteze rotunde, clauza se execută dacă se potrivește oricare dintre excepțiile din tuplu. conv("21") returnează 42, deoarece int("21") devine 21. conv("abc") este pasabil ca șir de caractere, dar nu poate fi interpretat ca număr, deci produce ValueError. conv(None) nu este de un tip pasabil lui int(), deci produce TypeError. Distincția — tip corect dar valoare improprie duce la ValueError, iar tip incorect de la bun început duce la TypeError — corespunde exact ordinii din răspuns.

Î8 | Re-lansarea

Care este rezultatul următorului cod, care folosește o excepție proprie și un raise fără argumente?

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. Se afișează doar inner, fără outer
  2. Se afișează cele 2 linii inner și outer Exception
  3. Se afișează cele 2 linii inner și outer LoadError
  4. Se afișează inner, apoi execuția se oprește cu LoadError
Răspuns corectC. Se afișează cele 2 linii inner și outer LoadError

Un raise scris fără argumente în interiorul unui except relansează, exact așa cum este, excepția aflată în curs de tratare, către apelant. Prin urmare, după afișarea lui inner, LoadError se propagă în afara lui load(), iar except Exception din exterior o preia. Cum LoadError moștenește din Exception, poate fi captată de except Exception, deci execuția nu se oprește. type(e).__name__ returnează numele clasei excepției efectiv lansate, deci se afișează LoadError, nu numele tipului clauzei care a captat-o, Exception. Varianta care conține doar inner ar corespunde cazului în care excepția era înghițită pe loc, fără relansare.

Î9 | Excepție necaptată

Care este rezultatul la rularea următorului cod?

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. Se afișează 2, apoi execuția se oprește cu KeyError
  2. Se afișează, pe 2 linii, 2 și not found
  3. Se afișează, pe 2 linii, 2 și KeyError
  4. Se afișează, pe 2 linii, 2 și None
Răspuns corectA. Se afișează 2, apoi execuția se oprește cu KeyError

Primul apel folosește indexul 1 al listei, deci returnează 2. Al doilea interoghează o cheie inexistentă într-un dicționar, deci produce KeyError, dar în except este scris doar IndexError. Deși IndexError și KeyError sunt amândouă subclase ale lui LookupError, nu sunt una părinte a celeilalte, deci un except pentru IndexError nu poate prinde KeyError. Excepția necaptată se propagă către apelant, iar cum nu există cine să o prindă, programul se oprește. Dacă se dorește captarea ambelor, se scrie except LookupError, sau se listează ambele într-un tuplu.

Î10 | assert

Care este descrierea corectă pentru instrucțiunea assert?

  1. Dacă condiția este falsă, lansează AssertionError, iar la python3 -O este dezactivată
  2. Dacă condiția este falsă, lansează AssertionError, iar la python3 -O se execută mereu
  3. Dacă condiția este adevărată, lansează AssertionError, iar la python3 -O este dezactivată
  4. Dacă condiția este falsă, lansează ValueError, iar la python3 -O se execută mereu
Răspuns corectA. Dacă condiția este falsă, lansează AssertionError, iar la python3 -O este dezactivată

assert condiție, mesaj lansează AssertionError atunci când condiția este falsă. Dacă este adevărată, nu se întâmplă nimic. Important este că, pornit în mod de optimizare, ca python3 -O, instrucțiunea assert este eliminată complet, fără să se execute deloc. Prin urmare, assert nu trebuie folosit pentru a verifica intrarea utilizatorului — pentru acest scop se verifică condiția printr-un if și se lansează raise ValueError(...). Este util să se rețină rolul lui assert ca instrument pentru a consemna, în timpul dezvoltării, presupuneri interne.

Î11 | self

Care este descrierea corectă pentru self, primul parametru al unei metode?

  1. Este un nume rezervat pentru a referi atributele clasei-părinte
  2. Este un argument căruia i se pasează automat chiar instanța care a apelat metoda
  3. Este un argument menit să creeze o instanță și să o returneze apelantului
  4. Este un nume rezervat, căruia i se pasează automat clasa însăși
Răspuns corectB. Este un argument căruia i se pasează automat chiar instanța care a apelat metoda

Scriind d.speak(), Python reinterpretează apelul ca Dog.speak(d). În această poziție, primul argument, ajunge chiar instanța care a făcut apelul. Ortografia self este doar o convenție, nu un nume rezervat de limbaj. Singura specificație este că primul argument primește obiectul însuși, deci și scris explicit Dog.speak(d), funcționează la fel. Crearea și returnarea unei instanțe este rolul lui __new__ și al mecanismului de apelare a clasei; __init__ doar inițializează o instanță deja creată. Pentru a referi clasa-părinte se folosește super().

Î12 | Variabila de clasă

Care este rezultatul următorului cod, care modifică o variabilă de clasă?

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
Răspuns corectA. wolf wolf

kind este o variabilă de clasă, scrisă direct sub class, iar toate instanțele create din acea clasă o partajează ca pe una singură. Nici a, nici b nu au kind în propriul __dict__, deci, la citirea atributului, se urcă până la nivelul clasei și se vede aceeași valoare. Atribuirea la Dog.kind modifică exact acea valoare partajată, deci atât a, cât și b, deja create, văd valoarea wolf. Valoarea nu este copiată la crearea instanței, deci nu apare varianta în care rămâne canine.

Î13 | Umbrirea unui atribut

Care este rezultatul următorului cod, în care se face o atribuire pe partea instanței?

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 și ['name']
  2. husky canine canine și ['kind', 'name']
  3. husky canine canine și ['name']
  4. husky husky husky și ['kind', 'name']
Răspuns corectB. husky canine canine și ['kind', 'name']

Atribuirea a.kind nu modifică variabila de clasă, ci creează un element nou, kind, în dicționarul instanței a. De atunci înainte, citirea lui a.kind găsește mai întâi acest element nou, umbrind variabila de clasă. b și Dog.kind rămân la valoarea originală, canine. Prin urmare, în a.__dict__ apare, pe lângă name, pus de __init__, și kind, iar sortat rezultă ['kind', 'name']. Varianta cu husky peste tot ar corespunde ideii greșite că atribuirea pe instanță modifică variabila de clasă, iar varianta cu doar ['name'] ar corespunde ideii că nu se creează nicio umbrire.

Î14 | Partajarea

Care este rezultatul următorului cod, care face dintr-o listă variabilă de clasă?

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 este [1], q.items este [1, 2]
  2. p.items este [1, 2], q.items este [2]
  3. p.items este [1, 2], q.items este [1, 2]
  4. p.items este [1], q.items este [2]
Răspuns corectC. p.items este [1, 2], q.items este [1, 2]

self.items.append(x) nu este o atribuire către self.items, ci doar apelează o metodă a obiectului la care ajunge self.items. Nici p, nici q nu au propriul items, deci ambele ajung la aceeași și unica listă, variabila de clasă. Ca urmare, atât 1 adăugat prin p, cât și 2 adăugat prin q, ajung în aceeași listă, iar, indiferent din ce parte se privește, rezultă [1, 2]. Dacă se dorește câte un recipient separat pentru fiecare instanță, acesta trebuie creat explicit în __init__, prin self.items = []. Astfel s-ar obține separat [1] și [2].

Î15 | super()

Care este rezultatul următorului cod, care folosește moștenirea și 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
Răspuns corectD. Tama meow black True

__init__ al lui Cat apelează super().__init__(name), deci Animal, clasa-părinte, este cel care stabilește self.name. După aceea se atribuie și self.color valoarea black, deci ambele atribute există. cry este redefinit în clasa-copil, deci implementarea din părinte este suprascrisă, iar cea care se apelează este meow. isinstance(c, Animal) este True, câtă vreme Cat moștenește din Animal. Dacă s-ar uita apelul super(), name nu ar fi setat și ar apărea AttributeError, deci nu se ajunge la o defecțiune tăcută, precum varianta cu None.

Î16 | MRO

Care este rezultatul următorului cod, cu moștenire multiplă?

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 și ['D', 'B', 'C', 'A', 'object']
  2. C și ['D', 'C', 'B', 'A', 'object']
  3. B și ['D', 'B', 'A', 'C', 'object']
  4. A și ['D', 'B', 'C', 'A', 'object']
Răspuns corectA. B și ['D', 'B', 'C', 'A', 'object']

Ordinea de rezolvare a metodelor este stabilită printr-o regulă numită liniarizare C3, nu prin simplă căutare în adâncime. Părintele comun A este plasat după toți copiii care moștenesc din A, deci ordinea devine D, B, C, A, object. Dacă s-ar face o căutare în adâncime, coborând mai întâi în B și ajungând direct la A, ar rezulta D, B, A, C, object, dar nu așa se întâmplă — acesta este punctul de atenție. who() caută de la începutul MRO și folosește prima apariție găsită, deci se returnează cea din B. Dacă s-ar scrie class E(C, B), ar rezulta E, C, B, A, object — clasa scrisă prima are prioritate.

Î17 | Contradicția în MRO

Dacă class B(A) este deja definită, ce se întâmplă dacă se scrie class F(A, B): pass?

  1. Definiția reușește, iar F().who() apelează implementarea din A
  2. Definiția reușește, iar __mro__ devine F, A, B, object
  3. Definiția reușește, iar F().who() apelează implementarea din B
  4. Se produce TypeError la momentul definirii clasei
Răspuns corectD. Se produce TypeError la momentul definirii clasei

Liniarizarea C3 impune două reguli: să se păstreze ordinea în care au fost enumerate clasele de bază și ca un părinte să fie plasat după copiii săi. F(A, B) cere ca A să fie plasat înaintea lui B, dar cum B este copilul lui A, părintele A trebuie să vină după copilul B. Aceste două cerințe nu pot fi satisfăcute simultan, deci, neputându-se construi un MRO consistent, se produce TypeError chiar la evaluarea definiției clasei. Eșecul nu apare abia la rulare, ci în chiar momentul în care se trece de instrucțiunea class. Forma corectă este class F(B, A), scriind copilul primul.

Î18 | Deformarea numelui (name mangling)

Care este rezultatul următorului cod, care are atribute ce încep cu underscore?

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 și ['_one', '_two']
  2. 1 2 și ['_P__one', '_P__two']
  3. 1 2 și ['__two', '_one']
  4. 1 2 și ['_P__two', '_one']
Răspuns corectD. 1 2 și ['_P__two', '_one']

_one, cu un singur underscore, indică prin convenție doar uz intern, fără să-și schimbe numele; poate fi citit normal din exterior. __two, cu doi underscore, este supus deformării numelui: numele efectiv al atributului devine _P__two, adică _NumeleClasei__nume. De aceea, scriind p.__two, apare AttributeError, dar scriind p._P__two, se poate citi. Și în __dict__ apare numele deja deformat, deci sortat rezultă ['_P__two', '_one']. Doar cele care încep cu doi underscore sunt deformate; _one nu este afectat.

Î19 | Metode speciale

Care este rezultatul următorului cod, care definește metode speciale?

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) și [Vec(x=1, y=2)] și 2 V(4, 6)
  2. V(1, 2) și [Vec(x=1, y=2)] și 2 V(4, 6)
  3. V(1, 2) și [Vec(x=1, y=2)] și 2 Vec(x=4, y=6)
  4. V(1, 2) și [V(1, 2)] și 2 V(4, 6)
Răspuns corectB. V(1, 2) și [Vec(x=1, y=2)] și 2 V(4, 6)

print(v) și str(v) folosesc __str__, deci rezultă V(1, 2). Pe de altă parte, afișarea în interiorul unui container, precum o listă sau un dicționar, folosește __repr__ pentru fiecare element, deci rezultă [Vec(x=1, y=2)]. Această distincție este esența întrebării. len(v) returnează valoarea dată de __len__, adică 2, iar v + Vec(3, 4) returnează un Vec nou, dat de __add__, care, pasat lui print, folosește din nou __str__, deci apare V(4, 6). Într-o clasă fără __str__ definit, se folosește în locul lui __repr__, dar niciodată invers, __str__ nu preia locul lui __repr__.

Î20 | __eq__

Care este rezultatul următorului cod, care definește doar __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, apoi False, apoi 2
  2. True False, apoi True, apoi TypeError
  3. True False, apoi False, apoi 1
  4. True True, apoi True, apoi TypeError
Răspuns corectB. True False, apoi True, apoi TypeError

Fiind definit __eq__, p == q compară atributul x și devine True, dar cum p și q sunt obiecte create separat, p is q rămâne False. Aici este esențial faptul că o clasă care definește __eq__ primește automat __hash__ setat la None, deci Point.__hash__ is None este True. Un obiect fără hash nu poate fi folosit ca element de mulțime sau ca cheie de dicționar, deci încercarea de a crea {p, q} produce TypeError. Dacă se dorește includerea și în mulțimi, trebuie definit manual un __hash__ care returnează aceeași valoare pentru obiecte egale.

Î21 | math

Care este rezultatul următorului cod, care folosește modulul math?

import mathprint(math.sqrt(16))print(math.floor(-1.5), math.ceil(-1.5))
  1. 4.0, apoi -2 -1
  2. 4.0, apoi -1 -2
  3. 4, apoi -2 -1
  4. 4, apoi -1 -1
Răspuns corectA. 4.0, apoi -2 -1

math.sqrt returnează float chiar și atunci când rezultatul este exact, deci rădăcina lui 16 se afișează ca 4.0, nu ca 4. math.floor rotunjește spre coada axei numerelor, deci -1.5 devine -2, iar math.ceil rotunjește spre capătul opus, deci -1.5 devine -1. Pentru numere negative, trebuie observat contrastul cu int(-1.5), care rotunjește spre 0 și dă -1, diferit de rezultatul lui floor. Dacă floor și ceil se confundă, rezultă ordinea -1 -2, iar dacă se crede că sqrt returnează int, rezultă 4.

Î22 | os.path

Care este combinația corectă a valorilor returnate de os.path.splitext('/tmp/z.txt') și os.path.basename('/tmp/z.txt')?

  1. ('/tmp/z', '.txt') și 'z.txt'
  2. ('/tmp', 'z.txt') și '/tmp'
  3. ('/tmp/z', 'txt') și 'z.txt'
  4. ('/tmp/z.txt', '') și 'z.txt'
Răspuns corectA. ('/tmp/z', '.txt') și 'z.txt'

os.path.splitext desparte doar extensia, păstrând punctul de partea extensiei, și returnează un tuplu cu 2 elemente. Prin urmare, rezultă ('/tmp/z', '.txt'). Punctul nu dispare, deci nu rezultă 'txt'. os.path.basename returnează numele de la sfârșitul căii, deci 'z.txt'. Cel care returnează partea de director, '/tmp', este os.path.dirname, iar dacă se dorește un tuplu cu ambele deodată, se folosește os.path.split. Este util să se rețină distincția: splitext taie după extensie, nu după separatorul de director.

Î23 | Potrivirea cu re

Care este rezultatul următorului cod, care folosește modulul re?

import reprint(re.match("b", "abc"))print(re.search("b", "abc").group())print(re.findall(r"\d+", "a1b22c333"))
  1. b, apoi None, apoi ['1', '22', '333']
  2. None, apoi b, apoi ['1', '22', '333']
  3. b, apoi b, apoi ['1', '22', '333']
  4. None, apoi b, apoi ['a1', 'b22', 'c333']
Răspuns corectB. None, apoi b, apoi ['1', '22', '333']

re.match potrivește doar de la începutul șirului, iar începutul lui 'abc' este 'a', care nu se potrivește cu 'b', deci returnează None. re.search caută și în interior, deci se potrivește cu 'b', iar .group() extrage șirul potrivit, b. Această diferență este cel mai adesea testată la modulul re. re.findall returnează o listă cu toate porțiunile care se potrivesc cu tiparul, iar \d+ înseamnă cifre consecutive, deci rezultă ['1', '22', '333']. Caracterele de separare nu sunt incluse. Dacă nu există nicio potrivire, se returnează o listă vidă.

Î24 | datetime

Care este rezultatul următorului cod, care folosește 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, apoi 2026/08/22, apoi 2026/09/01
  2. 2026-08-22, apoi 2026/08/22, apoi 2026-09-01
  3. 2026-08-22, apoi 2026/08/22, apoi 10 days, 0:00:00
  4. 2026-08-22, apoi 2026/08/22, apoi 2026-08-32
Răspuns corectB. 2026-08-22, apoi 2026/08/22, apoi 2026-09-01

Afișând direct un obiect date cu print, apare în formatul ISO, cu cratimă ca separator, 2026-08-22. Pentru a schimba formatul se folosește strftime, iar pasându-i '%Y/%m/%d' se obține șirul cu separator bară oblică, 2026/08/22. Adăugând timedelta(days=10) la un date, ceea ce se returnează nu este un timedelta, ci un nou date, indicând data cu 10 zile mai târziu, deci la print apare 2026-09-01. Calculul care trece peste sfârșitul lunii este gestionat automat de datetime, deci nu apare o dată inexistentă, precum 2026-08-32, obținută prin simpla adăugare a lui 10 la ziua curentă. Dacă s-ar crede că print(d) folosește același format ca strftime, ar rezulta separatorul cu bară oblică peste tot, dar afișarea implicită folosește cratimă. Scăzând între ele două date, se returnează un timedelta, nu un date, iar afișarea acestuia are o formă precum 10 days, 0:00:00.

Î25 | Counter

Care este rezultatul următorului cod, care folosește collections.Counter?

from collections import Counterc = Counter("abracadabra")print(c.most_common(2))print(c["a"], c["z"])
  1. [('a', 5), ('b', 2)] și 5, apoi KeyError
  2. [('a', 5), ('r', 2)] și 5 0
  3. [('a', 5), ('b', 2)] și 5 0
  4. [('a', 5), ('b', 2), ('r', 2)] și 5 0
Răspuns corectC. [('a', 5), ('b', 2)] și 5 0

'abracadabra' conține 5 litere a, câte 2 litere b și r, și câte 1 literă c și d. most_common(2) returnează doar primele 2, în ordine descrescătoare, sub formă de listă de tupluri (element, număr), deci rezultă [('a', 5), ('b', 2)]. Dintre b și r, care au același număr, se preferă cel apărut mai devreme, deci este ales b. Counter este un fel de dicționar, dar, spre deosebire de un dicționar obișnuit, interogarea unui element inexistent nu produce KeyError, ci returnează 0. Această proiectare permite să se numere fără a mai fi nevoie să se verifice în prealabil existența.

Î26 | namedtuple

Care este rezultatul următorului cod, care folosește un tip creat cu 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) și 1 1 True
  2. Pt(x=1, y=2) și 1 1 False
  3. Pt(x=1, y=2) și 1 1 True
  4. Pt(1, 2) și 1 1 False
Răspuns corectC. Pt(x=1, y=2) și 1 1 True

Tipul creat cu namedtuple, afișat, ia forma Pt(x=1, y=2), cu numele tipului și al câmpurilor. Valoarea poate fi citită atât după nume, cât și după index, deci p.x și p[0] sunt amândouă 1. Fiind un tip derivat din tuplu, isinstance(p, tuple) este True. Cu alte cuvinte, este un fel de tuplu, deci elementele nu pot fi rescrise ulterior. Dacă se dorește o valoare modificabilă, se folosește _replace, care creează o instanță nouă cu valoarea înlocuită, sau se definește o clasă obișnuită.

Î27 | Consumarea completă

Care este rezultatul următorului cod, care pasează același obiect map de două ori lui list()?

m = map(str, [1, 2, 3])print(list(m))print(list(m))
  1. Prima linie ['1', '2', '3'], a doua linie None
  2. Prima linie ['1', '2', '3'], a doua linie []
  3. Prima linie [1, 2, 3], a doua linie []
  4. Atât prima, cât și a doua linie, ['1', '2', '3']
Răspuns corectB. Prima linie ['1', '2', '3'], a doua linie []

map returnează nu o listă, ci un iterator, care oferă câte o valoare la fiecare next(), iar odată epuizat, rămâne așa. La primul list() se extrag toate cele 3 valori, deci al doilea list() nu mai are ce rămas, rezultând o listă vidă. Nu apare nici eroare, nici None; devine gol în tăcere, ceea ce e ușor de trecut cu vederea. map(str, ...) transformă fiecare element în șir de caractere, deci conținutul primei liste nu este numeric, ci ['1', '2', '3']. Același lucru se întâmplă cu filter, zip, enumerate și cu generatoarele, deci, dacă se dorește parcurgere de mai multe ori, se fixează imediat conținutul cu list().

Î28 | filter și all

Care este rezultatul următorului cod, care folosește filter, all și any?

print(list(filter(None, [0, 1, "", 2, None, "a"])))print(all([]), any([]))
  1. [1, 2, 'a'] și False False
  2. [0, '', None] și True False
  3. [1, 2, 'a'] și True False
  4. [1, 2, 'a'] și False True
Răspuns corectC. [1, 2, 'a'] și True False

Pasând None ca prim argument lui filter, filtrarea se face după valoarea de adevăr a elementului însuși. 0, șirul vid și None sunt evaluate ca false, deci sunt eliminate, iar rezultă [1, 2, 'a']. Nu rămân cele eliminate. all este specificat să returneze True atunci când nu există niciun element fals, deci pentru o listă complet vidă returnează True. any returnează True atunci când există cel puțin un element adevărat, deci pentru o listă vidă returnează False. Faptul că aceste două dau răspunsuri opuse pentru o secvență vidă este exact ce se testează aici.

Î29 | Generator

Se apelează o funcție ce conține yield, în codul următor. Care este ordinea rândurilor afișate?

def gen():    print("start")    yield 1    print("mid")    yield 2g = gen()print("created")print(next(g))print(next(g))
  1. În ordinea: start mid created 1 2
  2. În ordinea: created start mid 1 2
  3. În ordinea: created start 1 mid 2
  4. În ordinea: start created 1 mid 2
Răspuns corectC. În ordinea: created start 1 mid 2

Chiar și apelând o funcție ce conține yield, corpul nu se execută nici măcar o linie. Ceea ce se returnează este doar un obiect generator, deci created apare afișat primul. Abia la primul next(g), execuția pornește efectiv de la început, afișează start, apoi se oprește la yield 1, care returnează 1. Următorul next(g) reia exact de unde s-a oprit, afișează mid, apoi se oprește la yield 2, care returnează 2. Această execuție amânată este diferența decisivă față de o funcție obișnuită; dacă se mai apelează încă o dată next(g), funcția se termină și apare StopIteration.

Î30 | is și ==

Care este rezultatul următorului cod, care compară operatorii is și ==?

a = [1, 2]b = [1, 2]c = aprint(a == b, a is b)print(a == c, a is c)
  1. True False, apoi True False
  2. True True, apoi True True
  3. False False, apoi True True
  4. True False, apoi True True
Răspuns corectD. True False, apoi True True

== este operatorul care întreabă dacă valorile sunt egale, deci pentru a și b, care au același conținut, rezultă True. is este operatorul care întreabă dacă este vorba de același obiect, iar a și b sunt două liste create separat, deci rezultă False. Pe de altă parte, c = a nu creează o listă nouă, ci doar atașează un alt nume aceluiași obiect, deci a is c este True. Cu alte cuvinte, dacă se modifică lista prin c, schimbarea se vede și prin a. None este un obiect special, existent într-o singură instanță, deci scrierea x is None este convenția corectă, dar pentru a compara valori se folosește întotdeauna ==.

Exersare: rezolvați întrebările de pe această pagină

Acesta este un instrument de exersare cu întrebări în ordine aleatorie (funcționează când JavaScript este activat). Puteți citi oricum toate întrebările și explicațiile de mai sus.

* Explicațiile sunt informații în scop de studiu. Tematica și sistemul examenelor se schimbă de la an la an, așa că verificați întotdeauna anunțurile oficiale ale organizatorului examenului.

Această pagină este o traducere a textului original în japoneză. Dacă traducerea diferă de original, prevalează versiunea în japoneză. Vedeți originalul în japoneză