Mål Python-koden din: Bruk timeit effektivt!

I denne veiledningen skal vi undersøke hvordan man anvender timeit-funksjonen fra Pythons timeit-modul. Vi vil se på hvordan man kan måle tidsbruken for enkle uttrykk og funksjoner i Python.

Å analysere hvor lang tid koden din bruker på å utføre, gir deg et overslag over kjøretiden for et bestemt kodeområde. Dette kan hjelpe deg med å identifisere deler av koden som kan trenge optimalisering.

Vi starter med å se på hvordan timeit-funksjonen i Python er bygd opp, før vi fortsetter med praktiske eksempler. Målet er å forstå hvordan du kan bruke modulen til å måle ytelsen til både kodeblokker og funksjoner. La oss komme i gang!

Hvordan bruke Python timeit-funksjonen

timeit-modulen er en standard del av Python-biblioteket, og den kan enkelt importeres:

import timeit

Slik er syntaksen for timeit-funksjonen:

timeit.timeit(stmt, setup, number)

Hvor:

  • stmt er koden hvis utførelsestid vi ønsker å måle. Dette kan være en enkelt linje, flere linjer eller navnet på en funksjon.
  • setup er koden som kjøres én gang før stmt. Dette kan være nødvendig for å klargjøre miljøet for kodebiten som skal testes. For eksempel, hvis du tester tiden det tar å lage en NumPy-matrise, er import av NumPy en del av setup.
  • number bestemmer hvor mange ganger stmt skal kjøres. Standardverdien er 1 000 000, men du kan selv velge en annen verdi.

Nå som vi har sett på syntaksen, la oss se noen eksempler i praksis.

Måling av enkle Python-uttrykk

I dette avsnittet skal vi bruke timeit for å måle ytelsen til enkle Python-uttrykk.

Start en Python REPL og kjør følgende kode. Vi skal måle tidsbruken for eksponentiering og heltallsdivisjon, med 10 000 og 100 000 repeteringer.

Merk at uttrykkene som skal måles sendes inn som en Python-streng, der semikolon brukes for å skille de forskjellige uttrykkene.

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

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

Kjøre Python timeit fra kommandolinjen

Du kan også bruke timeit direkte fra kommandolinjen. Kommandoen tilsvarer timeit-funksjonskallet slik:

$ python -m timeit -n [number] -s [setup] [stmt]
  • python -m timeit angir at vi kjører timeit som hovedmodul.
  • -n er en kommando som angir hvor mange ganger koden skal kjøres. Dette tilsvarer parameteren number i timeit().
  • -s brukes til å angi setup-koden.

Her er en ny versjon av forrige eksempel, denne gangen med kommandolinjen:

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

I dette eksemplet måler vi ytelsen til den innebygde len()-funksjonen. Initialiseringen av strengen er setup-koden som legges inn med -s-alternativet.

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

Legg merke til at resultatet viser den beste tiden av 5 kjøringer. Når du kjører timeit via kommandolinjen er -r-alternativet (antall repeteringer) satt til standardverdien 5. Det betyr at stmt kjøres flere ganger, og den beste tiden blir vist.

Analyse av metoder for å snu strenger med timeit

Når du arbeider med strenger i Python, kan du ha behov for å snu dem. De to vanligste metodene for å snu en streng er:

  • Bruke streng-slicing
  • Bruke reversed()-funksjonen og join()-metoden

Snu Python-strenger ved bruk av streng-slicing

La oss se hvordan streng-slicing fungerer og hvordan du kan bruke det til å snu en streng. Syntaksen en_streng[start:stopp] returnerer en del av strengen som starter ved start og går frem til, men ikke inkludert, stopp-indeksen. La oss se på et eksempel.

Ta for eksempel strengen «Python». Strengen har lengde 6 og indeksene går fra 0, 1, 2 og opp til 5.

>>> string_1 = 'Python'

Når du spesifiserer både start– og stopp-verdien, får du en strengbit som går fra start til stopp – 1. Derfor vil string_1[1:4] returnere «yth».

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

Dersom du ikke spesifiserer start-verdien, brukes standardverdien 0. Da starter utsnittet ved indeks 0 og går frem til stopp – 1.

Her er stopp-verdien 3, så utsnittet starter ved indeks 0 og går frem til indeks 2.

>>> string_1[:3]
  'Pyt'

Hvis du ikke inkluderer stopp-indeksen, starter utsnittet ved start-indeksen (1) og går frem til slutten av strengen.

>>> string_1[1:]
  'ython'

Hvis du hopper over både start– og stopp-verdien, får du hele strengen.

>>> string_1[::]
  'Python'

La oss lage et utsnitt med en steg-verdi. Hvis start, stopp og steg-verdiene er henholdsvis 1, 5 og 2, får vi et utsnitt som starter ved 1, går til 4 (ikke inkludert 5) og inkluderer annethvert tegn.

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

Med en negativ steg-verdi, kan du lage et utsnitt som starter på slutten av strengen. Med steg satt til -2, vil string_1[5:2:-2] gi følgende utsnitt:

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

For å få en omvendt kopi av strengen hopper vi over start– og stopp-verdiene og setter steg til -1, slik:

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

Oppsummert: streng[::-1] returnerer en omvendt kopi av strengen.

Snu strenger med innebygde funksjoner og strengmetoder

Den innebygde funksjonen reversed() i Python returnerer en reversert iterator over elementene i strengen.

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

Du kan iterere gjennom den omvendte iteratoren ved hjelp av en for-løkke:

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

Dette vil gi deg tilgang til elementene i strengen i omvendt rekkefølge.

     # Output
     n
     o
     h
     t
     y
     P
     
    

Deretter kan du bruke join()-metoden på den omvendte iteratoren med syntaksen: <sep>.join(reversed(en_streng)).

Kodebiten under viser et par eksempler der skilletegnet er henholdsvis en bindestrek og et mellomrom.

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

I dette tilfellet ønsker vi ikke noe skilletegn. Derfor setter vi skilletegnet til en tom streng for å få en omvendt kopi av strengen:

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

Ved å bruke ''.join(reversed(en_streng)) returneres en reversert kopi av strengen.

Sammenligne kjøretid med timeit

Nå har vi sett to metoder for å reversere en streng. Men hvilken er raskest? La oss finne det ut.

I det tidligere eksemplet der vi målte enkle Python-uttrykk, trengte vi ikke setup-kode. Men i dette tilfellet snur vi en Python-streng. Selv om snuoperasjonen kjøres det antall ganger som er spesifisert med number, kjøres initialiseringen av strengen kun én gang.

     
     >>> 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
    
    

For samme antall repetisjoner er det å snu strengen ved hjelp av slicing raskere enn å bruke join()-metoden og reversed()-funksjonen.

Måling av Python-funksjoner med timeit

I dette avsnittet skal vi se på hvordan man kan måle ytelsen til Python-funksjoner med timeit-funksjonen. Gitt en liste med strenger, returnerer følgende funksjon en liste over strenger som inneholder minst ett siffer.

     
     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
     
    

Vi ønsker å måle tidsbruken til Python-funksjonen hasDigit() med timeit.

Først må vi identifisere hva som skal måles, altså stmt. Det er kallet til funksjonen hasDigit() med en liste over strenger som argument. Deretter må vi definere setup-koden. Hva skal denne bestå av?

For at funksjonskallet skal fungere må setup-koden inneholde:

  • Definisjonen av funksjonen hasDigit()
  • Initialiseringen av argumentlisten med strenger

La oss definere setup-koden i en string, slik:

     
      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']
           """
      
    

Nå kan vi bruke timeit-funksjonen og få tidsbruken for hasDigit()-funksjonen for 100 000 repetisjoner.

     
     import timeit
     timeit.timeit('hasDigit(thislist)',setup=setup,number=100000)
      
    
     
     # Output
     0.2810094920000097
     
    

Konklusjon

Du har lært hvordan du kan bruke Pythons timeit-funksjon for å måle tidsbruken til uttrykk, funksjoner og andre objekter som kan kalles. Dette kan være nyttig for å teste koden din, sammenligne ytelser av ulike metoder for det samme, og mer.

La oss oppsummere hva vi har gått gjennom i denne veiledningen. Du kan bruke timeit()-funksjonen med syntaksen timeit.timeit(stmt=...,setup=...,number=...). Alternativt kan du kjøre timeit fra kommandolinjen for å måle ytelsen til korte kodebiter.

Som et neste steg kan du utforske andre Python-profileringsverktøy som line-profiler og mem-profiler for å analysere koden din for henholdsvis tidsbruk og minnebruk.

Til slutt, lærer du hvordan du kan beregne tidsforskjeller i Python.