Utilizarea Python Timeit pentru a-ți cronometra codul

În cadrul acestui ghid, vei descoperi cum să te folosești de funcția `timeit` din modulul cu același nume al limbajului Python. Vei înțelege cum să măsori durata de execuție a expresiilor și a funcțiilor simple în Python.

Măsurarea timpului de execuție al codului este un instrument valoros pentru a estima cât de mult durează să ruleze anumite secțiuni de cod și pentru a identifica zonele care necesită optimizare.

Vom începe prin a analiza structura funcției `timeit`, după care vom implementa exemple concrete pentru a înțelege cum să o utilizezi în vederea măsurării timpului de execuție al blocurilor de cod și funcțiilor din propriile tale module Python. Să începem!

Cum folosim funcția `timeit` în Python

Modulul `timeit` este inclus în biblioteca standard Python, putând fi importat simplu:

import timeit

Sintaxa de bază pentru utilizarea funcției `timeit` din modulul `timeit` este următoarea:

timeit.timeit(stmt, setup, number)

Unde:

  • `stmt` reprezintă secțiunea de cod al cărei timp de execuție se dorește a fi măsurat. Poți specifica codul ca un șir simplu, ca un șir multilinie, sau poți indica direct numele unei funcții.
  • `setup`, după cum sugerează și numele, reprezintă codul care trebuie rulat o singură dată, adesea ca o etapă premergătoare rulării lui `stmt`. De exemplu, dacă vrei să măsori cât durează crearea unui tablou NumPy, atunci importarea modulului `numpy` ar fi codul de configurare, iar crearea efectivă a tabloului ar fi instrucțiunea ce urmează a fi cronometrată.
  • Parametrul `number` indică de câte ori este executat `stmt`. Valoarea implicită este de 1 milion (1000000), dar poți ajusta acest parametru la orice valoare preferi.

Acum că am înțeles sintaxa funcției `timeit()`, vom trece la exemple practice.

Măsurarea timpului expresiilor simple în Python

În această secțiune, vom explora cum putem măsura durata de execuție a expresiilor simple Python folosind `timeit`.

Deschide un interpretor Python (REPL) și execută următoarele exemple. Vom calcula cât durează operațiunile de ridicare la putere și împărțire întreagă, pentru 10000 și respectiv 100000 de repetări.

Reține că instrucțiunea de măsurat este furnizată ca un șir Python, folosind punct și virgulă pentru a separa expresiile multiple în cadrul instrucțiunii.

>>> import timeit
>>> timeit.timeit('3**4;3//4',number=10000)
0.0004020999999738706

>>> timeit.timeit('3**4;3//4',number=100000)
0.0013780000000451764

Rularea `timeit` din linia de comandă

Funcția `timeit` poate fi utilizată și din linia de comandă. Iată echivalentul din linia de comandă al apelului funcției `timeit`:

$ python -m timeit -n [number] -s [setup] [stmt]
  • `python -m timeit` indică faptul că rulăm `timeit` ca modul principal.
  • `-n` este opțiunea care specifică de câte ori trebuie să ruleze codul, echivalentă cu parametrul `number` din apelul funcției `timeit()`.
  • Opțiunea `-s` permite definirea codului de configurare.

În continuare, vom rescrie exemplul anterior folosind linia de comandă:

$ python -m timeit -n 100000 '3**4;3//4'
100000 loops, best of 5: 35.8 nsec per loop

În exemplul de mai jos, vom măsura timpul de execuție al funcției `len()`. Inițializarea șirului este codul de configurare, transmis prin opțiunea `-s`.

$ python -m timeit -n 100000 -s "string_1 = 'coding'" 'len(string_1)'
100000 loops, best of 5: 239 nsec per loop

În rezultat, observăm timpul de execuție cel mai bun dintre 5 rulări. Ce înseamnă asta? Când execuți `timeit` din linia de comandă, opțiunea `-r` (pentru repetare) are valoarea implicită de 5. Adică, execuția lui `stmt` pentru numărul specificat de ori este repetată de cinci ori, iar rezultatul afișat este cel mai bun timp de execuție obținut.

Analiza metodelor de inversare a șirurilor cu `timeit`

Când lucrezi cu șiruri în Python, este posibil să vrei să le inversezi. Cele mai comune metode pentru a realiza acest lucru sunt:

  • Folosirea feliilor de șir
  • Utilizarea funcției `reversed()` împreună cu metoda `join()`

Inversarea șirurilor folosind felierea (slicing)

Vom vedea cum funcționează felierea și cum o putem utiliza pentru a inversa un șir Python. Folosind sintaxa `some-string[start:stop]`, se obține o porțiune a șirului începând de la indexul `start` și extinzându-se până la indexul `stop` – 1. Să analizăm un exemplu.

Considerăm șirul „Python”. Acesta are o lungime de 6, iar indicii sunt 0, 1, 2 până la 5.

>>> string_1 = 'Python'

Specificând atât valorile `start`, cât și `stop`, se obține o porțiune de șir care începe de la `start` și se extinde până la `stop` – 1. De exemplu, `string_1[1:4]` va returna „yth”.

>>> string_1 = 'Python'
>>> string_1[1:4]
'yth'

Când nu specifici valoarea de pornire, se folosește valoarea implicită de zero, iar felia începe de la indexul 0 și se extinde până la `stop` – 1.

Aici, valoarea de `stop` este 3, astfel că felia va începe de la indexul 0 și va ajunge până la indexul 2.

>>> string_1[:3]
'Pyt'

Când nu specifici indexul de stop, felia începe de la indexul `start` (1) și se extinde până la sfârșitul șirului.

>>> string_1[1:]
'ython'

Dacă nu specifici nici valorile de `start`, nici de `stop`, felia va returna o copie a întregului șir.

>>> string_1[::]
'Python'

Să creăm o felie cu valoarea pasului. Setăm valorile `start`, `stop` și `step` la 1, 5 și, respectiv, 2. Obținem o porțiune de șir care începe de la 1, se extinde până la 4 (excluzând 5) și conține fiecare al doilea caracter.

>>> string_1[1:5:2]
'yh'

Folosind un pas negativ, poți obține o felie începând de la sfârșitul șirului. Cu pasul setat la -2, `string_1[5:2:-2]` va returna următoarea felie:

>>> string_1[5:2:-2]
'nh'

Pentru a obține o copie inversată a șirului, omitem valorile de `start` și `stop` și setăm pasul la -1, așa cum este indicat mai jos:

>>> string_1[::-1]
'nohtyP'

În concluzie, `șir[::-1]` returnează o copie inversată a șirului.

Inversarea șirurilor cu funcții predefinite și metode ale șirurilor

Funcția predefinită `reversed()` din Python returnează un iterator invers peste elementele șirului.

>>> string_1 = 'Python'
>>> reversed(string_1)
<reversed object at 0x00BEAF70>

Poți parcurge iteratorul invers folosind un ciclu `for`:

for char in reversed(string_1):
    print(char)

Și accesa elementele șirului în ordine inversă.

# Ieșire
n
o
h
t
y
P

Poți apela metoda `join()` asupra iteratorului invers cu sintaxa: `.join(reversed(some-string))`.

Secvența de cod de mai jos ilustrează câteva exemple, unde separatorul este cratimă și, respectiv, spațiu gol.

>>> '-'.join(reversed(string1))
'n-o-h-t-y-P'
>>> ' '.join(reversed(string1))
'n o h t y P'

În cazul în care nu dorim niciun separator, setăm separatorul ca șir gol, obținând astfel o copie inversată a șirului:

>>> ''.join(reversed(string1))
'nohtyP'

Utilizarea `””.join(reversed(some-string))` returnează o copie inversată a șirului.

Compararea timpilor de execuție cu `timeit`

Până acum am analizat două metode pentru inversarea șirurilor Python. Dar care dintre ele este mai rapidă? Să aflăm.

În exemplul anterior în care am măsurat timpul de execuție al expresiilor Python, nu am avut nevoie de cod de configurare. De această dată, vom inversa un șir Python. Operația de inversare a șirului va fi executată de numărul de ori specificat, iar codul de configurare va fi inițializarea șirului, care va fi rulată o singură dată.

>>> import timeit
>>> timeit.timeit(stmt="string_1[::-1]", setup = "string_1 = 'Python'", number = 100000)
0.04951830000001678
>>> timeit.timeit(stmt = "''.join(reversed(string_1))", setup = "string_1 = 'Python'", number = 100000)
0.12858760000000302

Pentru același număr de rulări, inversarea șirului prin feliere este mai rapidă decât utilizarea metodei `join()` și a funcției `reversed()`.

Măsurarea duratei funcțiilor Python cu `timeit`

În această secțiune vom învăța cum să măsurăm timpul de execuție a funcțiilor Python cu ajutorul `timeit`. Având în vedere o listă de șiruri, următoarea funcție `hasDigit` returnează lista de șiruri care conțin cel puțin o cifră.

def hasDigit(somelist):
     str_with_digit = []
     for string in somelist:
         check_char = [char.isdigit() for char in string]
         if any(check_char):
            str_with_digit.append(string)
     return str_with_digit

Vrem să măsurăm timpul de execuție al acestei funcții folosind `timeit`.

Mai întâi, vom defini instrucțiunea care va fi măsurată (stmt). Aceasta este apelul funcției `hasDigit()` cu o listă de șiruri ca argument. Apoi, vom defini codul de configurare. Care crezi că ar trebui să fie acesta?

Pentru ca apelul funcției să reușească, codul de configurare va trebui să includă:

  • Definiția funcției `hasDigit()`
  • Inițializarea listei de șiruri

Vom defini codul de configurare ca un șir, așa cum este arătat mai jos:

setup = """
def hasDigit(somelist):
    str_with_digit = []
    for string in somelist:
      check_char = [char.isdigit() for char in string]
      if any(check_char):
        str_with_digit.append(string)
    return str_with_digit
thislist=['puffin3','7frost','blue']
     """

Putem folosi apoi funcția `timeit` pentru a obține timpul de execuție al funcției `hasDigit()` pentru 100000 de repetări.

import timeit
timeit.timeit('hasDigit(thislist)',setup=setup,number=100000)
# Ieșire
0.2810094920000097

Concluzie

Ai învățat cum să folosești funcția `timeit` din Python pentru a măsura durata de execuție a expresiilor, funcțiilor și a altor elemente executabile. Acest lucru te poate ajuta să îți compari codul, să analizezi timpul de execuție al diferitelor implementări ale aceleiași funcții, și multe altele.

Să recapitulăm ceea ce am învățat în cadrul acestui ghid. Poți utiliza funcția `timeit()` cu sintaxa `timeit.timeit(stmt=…,setup=…,number=…)`. Alternativ, poți executa `timeit` direct din linia de comandă pentru a măsura fragmente de cod mai scurte.

Pentru pași următori, poți explora și alte pachete de profilare Python, precum `line-profiler` și `memprofiler`, pentru a-ți profila codul în funcție de timp și respectiv memorie.

Apoi, te invităm să afli mai multe despre calculul diferenței de timp în Python.