Jeg vil i denne guiden fortsette fra Del 1 hvor jeg introduserte begrepet idiomatisk programmering, samt tok for meg noen enkle eksempler. Så jeg vil anbefale å lese gjennom del 1 før du starter på denne.
Her i del 2 vil jeg ta for noen idiomer rundt riktig bruk av løkker i python, samt snakker om list-comprehension og generator funksjoner. Jeg vil og ta for meg noen idiomer rundt riktig bruk av dictionarys i python.
Løkker i python
Som i de fleste språk har python både while-løkker og for-løkker. While-løkkene fungerer som i alle andre språk og krever egentlig veldig lite ekstra forklaring. Det eneste er at man prøver gjerne å unngå de å heller bruke for-løkker i python så lenge det lar seg gjøre.
For-løkkene fungerer derimot på en annen måte i python enn det gjør i andre språk, som dere som bruker python nok allerede er fullstendig klar over. For-løkken er rett og slett det som blir kallt "For-each" i de fleste andre språk, og fungerer ved at den bruker en iterator.
Det første man lærer er gjerne å skrive for-løkker på denne måten:
Så for å forklare hvordan (og hvorfor) dette skal gjøres ideomatisk vil jeg starte med å forklare hva dette faktisk gjør.
Det første som skjer er at range-funksjonen lager en liste med tallene 0 til og med 5, videre lages det en iterator for denne listen, som den for hvert steg i forloopen går et hakk videre i listen. Dette betyr at denne koden ville vært det samme som
Det å bruke range-funksjonen på denne måten er ganske enkelt og fint - men gjør noe som er totalt unødvendig: den lager en liste du egentlig ikke trenger. Denne listen blir lagret i minne, og i tillegg gjør at koden blir tregere. For å gjøre dette mer ideomatisk vil man i stede bare bruke en iterator direkte. Dette gjør man ved å bruke xrange-funksjonen i stede.
xrange-funksjonen returnerer en iterator direkte, i stede for å lage en liste, som kun lagrer i minne hvilken verdi den har nå, ved hvilken verdi den skal stoppe og hvor store steg den skal ta hver gang. Dette gjør at koden din blir både mye raskere samt bruker en god del mindre minner når du skal iterere over store tallmengder.
Det er viktig å bemerke at dette med range og xrange her er spesifikt for python 2.x, i python 3.x så har range-funksjonen blitt fjernet og erstattet med xrange-funksjonen - slik at man alltid skal bruke iteratoren.
Det er også andre ting som er små feil som jeg merker folk med C-bakgrunn gjerne gjør i python - men som er veldig stygt. Skal ta for meg noen korte og enkle eksempler her.
Folk som skal iterere over en liste ser jeg av og til gjøre det på C-måten.
Dette er veldig feil måte å gjøre ting på i python, i stede vil man heller gjøre dette:
-----
Et annet eksempel er om man ønsker å gå gjennom listen baklengs, så ser man folk gjøre dette
Dette er akkurat samme feil som før, man skal ikke bruke index. Derfor gjør man heller dette:
-----
En annen ting man kommer over i python, hvor jeg gjerne ser folk gjøre det feil. (spesielt blandt mine studiekamerater), er når man skal iterere over en liste og bruke både indexen og elementet samtidig. Her ser man ofte kode som dette:
Dette er feil måte å gjøre det på, i stede skal man bruke funksjonen "enumerate":
-----
Eller hva om man ønsker å interere over 2 lister samtidig? Her er det veldig mange som gjør feil, og jeg ser ofte kode som minner noe lignende dette:
I slike tilfeller har man er funksjon som heter zip, fom komvinerer elementene fra de ulike listene til tupler. Dette gir mye finere kode.
Men her får vi samme feil som vi snakket om i begynnelsen, zip-funksjonen lager en ny liste som må lagres i minne. Om denne listen er stor får den ikke plass på cachen, noe som gjør at hele løkka blir treg. For å unngå dette gjør man akkurat det samme som man gjorde tidliggere: man bruker iteratoren direkte. Til dette har python en funksjon som heter izip, som lager en iterator i stede.
I python er det greit å ha som huskeregel at nesten alltid når man bruker indexer så gjør man noe feil. Python har som idiom at de prøver å gjøre slik at du ikke trenger å tenke på indexer - kun enkelte unntak. Meh hver gang du bruker en index, så prøv å se om du kan gjøre det på en finere måte uten.
Bytte ut while-løkker med for-løkker
En ting python er veldig glad i er å bytte ut while-løkker med for-løkker, og rett og slett prøve å komme seg vekk fra disse. Til dette har de laget en funksjon som heter "iter", som vil kalle en bestemt funksjon for hver gang den intererer, frem til den får en bestemt sentinell-verdi som får den til å terminere og avlutte løkken. Det letteste tilfellet hvor man kan vise nytteverdien av dette er når man skal lese data fra en stream (fil eller nettverk).
Dette kan man gjøre mye finere med en for-løkke, og man kan da bruke noe lignende dette:
Her innførte jeg enda litt magi, selve partial-funksjonen, som kanskje ikke alle av dere har sett før. Årsaken til dette er at iter-funksjonen tar inn en funksjon som første argument - men denne funksjonen blir kallt uten noen argumenter. Siden read-funksjonen tar som input hvor mange bytes den skal lese bruke jeg her partial til å lage en "wrapper-funksjon" som er en funksjon uten argumenter som kaller f.read med argumentene jeg da den.
Else-statement i for-løkker
Dette er en ting jeg tror veldig mange ikke er klar over - men i python har man muligheten til å ha en else-del på en forløkke, dette er noe man kan bruke til sin fordel. Else-delen av en forløkke blir kjørt etter den har iterert over alle elementene - den blir ikke kjørt når du bruker break til å hoppe ut av løkken.
Dette betyr at man kan lage følgende funksjon for å finne alle primtall under 100:
List-comprehensions
List-comprehension er en kort og konsis måte man kan lage nye eller transformere eksiterende lister på. Fordelen med list-comprehension er at man kan ta for-løkker man bruker til å generere ulike lister ned til en linje som er lett å lese. I tillegg vil de av og til også yte betydelig bedre enn andre måte å generere listene på. Syntaxen er og veldig enkel:
Dette kan man bruke til å f.eks. lage en liste med kvadratene av alle oddetall under 100
Dette kan også gjøres for både sett og dictionarys i python. For å lage et sett bruke man krøllparanteser i stede.
For dictionary så brukes også krøllparanteser, men da må man i tillegg putte på en nøkkel hver gang man lager et nytt element.
Dette er veldig enkle ting, som man gjerne får bruk for veldig ofte. Og så fort man blir vandt med å både skrive og lese list-comprehensions kan man gjøre koden betydelig finere. Så dette er en ting jeg bare anbefaler alle å bli vant med å bruke med en gang om dere planlegger å skrive litt pythonkode.
Dictionarys - noen idiomer
En ting man ofte gjør i python er å bruke nøkkelene i dictionaryen. Når man skal sjekke om en nøkkel eksisterer i et dictinary er det enkelt å gjøre
men dette er feil måte i python, i stede bruk IN
Det samme gjelder når man ønsker å iterere over nøkkelene, da skal du gjøre dette
ikke dette:
Her finnes det et unntak om man skal mutere selve dictionarien i løkken, da må du bruke dict.keys() siden det returnerer en liste men nøkkelene, ellers kan du risikere å få en RunetimeException.
Men hva om du ønsker å interere over både verdien og nøkkelen samtidig. Den enkle måten å gjøre dette på blir da rett frem dette:
Dette er feil måte i python, årsaken er rett og slett at du her må slå opp verdien hver gang - og hashe nøkkelen fler ganger enn nødvendig. Derfor har man en items() funksjon som hjelper det med dette:
Men her får vi samme problem som vi har hatt før: items-funksjonen lager en liste hvor vi egentlig ikke trenger listen, vi trenger kun iteratoren. Derfor har vi iteritems:
Settere og gettere i dictionary
Ta et program hvor du f.eks. ønsker å telle forekomsten av hver frukt i en fruktkurv, dette er noe som enkelt kan gjøres med følgende kode:
Dette er en enkel måte å gjøre det på, og er den måten de fleste av oss lærer først. Men når man blir litt bedre kjent med språket er det andre måter jeg vil anbefale. Det første er å bruke get-metoden, hvor vi kan legge ved en defaultverdi som blir returnert om elementet ikke er i dictionaryen.
Dette kan også forbedres videre med å bruke default-dict i stede for en vanlig dictionary:
--------------
Håper et par av dere lærte noe nytt av dette, og fant noen trikt dere ikke var klar over. Om noe er uklart eller dere har noen spørsmål, kom med de i tråden her - så skal jeg (eller noen andre pythonfolk) gjøre vårt beste for å svare.
I den neste delen vil jeg ta for meg lambda-funksjoner, dekarator-funksjoner, bruk av gettere og settere i python og hvordan man kan lage funksjoner med variabelt antall argumenter.
Her i del 2 vil jeg ta for noen idiomer rundt riktig bruk av løkker i python, samt snakker om list-comprehension og generator funksjoner. Jeg vil og ta for meg noen idiomer rundt riktig bruk av dictionarys i python.
Løkker i python
Som i de fleste språk har python både while-løkker og for-løkker. While-løkkene fungerer som i alle andre språk og krever egentlig veldig lite ekstra forklaring. Det eneste er at man prøver gjerne å unngå de å heller bruke for-løkker i python så lenge det lar seg gjøre.
For-løkkene fungerer derimot på en annen måte i python enn det gjør i andre språk, som dere som bruker python nok allerede er fullstendig klar over. For-løkken er rett og slett det som blir kallt "For-each" i de fleste andre språk, og fungerer ved at den bruker en iterator.
Det første man lærer er gjerne å skrive for-løkker på denne måten:
Kode
for x in range(5): print x
Det første som skjer er at range-funksjonen lager en liste med tallene 0 til og med 5, videre lages det en iterator for denne listen, som den for hvert steg i forloopen går et hakk videre i listen. Dette betyr at denne koden ville vært det samme som
Kode
for x in [0, 1, 2, 3, 4]: print x
Kode
for x in xrange(5): print x
Det er viktig å bemerke at dette med range og xrange her er spesifikt for python 2.x, i python 3.x så har range-funksjonen blitt fjernet og erstattet med xrange-funksjonen - slik at man alltid skal bruke iteratoren.
Det er også andre ting som er små feil som jeg merker folk med C-bakgrunn gjerne gjør i python - men som er veldig stygt. Skal ta for meg noen korte og enkle eksempler her.
Folk som skal iterere over en liste ser jeg av og til gjøre det på C-måten.
Kode
frukter = ["eple", "banan", "pære"] for i in range(len(frukter)): print frukter[i]
Kode
for frukt in frukter: print frukt
Et annet eksempel er om man ønsker å gå gjennom listen baklengs, så ser man folk gjøre dette
Kode
for i in range(len(frukter)-1, -1, -1): print frukter[i]
Kode
for frukt in reversed(frukter): print frukt
En annen ting man kommer over i python, hvor jeg gjerne ser folk gjøre det feil. (spesielt blandt mine studiekamerater), er når man skal iterere over en liste og bruke både indexen og elementet samtidig. Her ser man ofte kode som dette:
Kode
for i in range(len(frukter)): print i, ":", frukter[i]
Kode
for index, frukt in enumerate(frukter): print index, ":", frukt
Eller hva om man ønsker å interere over 2 lister samtidig? Her er det veldig mange som gjør feil, og jeg ser ofte kode som minner noe lignende dette:
Kode
frukter = ["eple", "banan", "pære"] farger = ["rød, "grønn", "blå", "gul"] n = min(len(frukter), len(farger)) for i in range(n): print frukter[i], farger[i]
Kode
for frukt, farge in zip(frukter, farger): print frukt, farge
Kode
for frukt, farge in izip(frukter, farger): print frukt, farge
Bytte ut while-løkker med for-løkker
En ting python er veldig glad i er å bytte ut while-løkker med for-løkker, og rett og slett prøve å komme seg vekk fra disse. Til dette har de laget en funksjon som heter "iter", som vil kalle en bestemt funksjon for hver gang den intererer, frem til den får en bestemt sentinell-verdi som får den til å terminere og avlutte løkken. Det letteste tilfellet hvor man kan vise nytteverdien av dette er når man skal lese data fra en stream (fil eller nettverk).
Kode
blocks = [] while True: block = f.read(32) if block == "": break blocks.append(block) print "".join(blocks)
Kode
blocks = [] for block in iter(partial(f.read, 32), ""): blocks.append(block) print "".join(blocks)
Else-statement i for-løkker
Dette er en ting jeg tror veldig mange ikke er klar over - men i python har man muligheten til å ha en else-del på en forløkke, dette er noe man kan bruke til sin fordel. Else-delen av en forløkke blir kjørt etter den har iterert over alle elementene - den blir ikke kjørt når du bruker break til å hoppe ut av løkken.
Dette betyr at man kan lage følgende funksjon for å finne alle primtall under 100:
Kode
for x in xrange(2, 100): for y in xrange(2, x): if x % y == 0: break else: print x, "is a prime number"
List-comprehensions
List-comprehension er en kort og konsis måte man kan lage nye eller transformere eksiterende lister på. Fordelen med list-comprehension er at man kan ta for-løkker man bruker til å generere ulike lister ned til en linje som er lett å lese. I tillegg vil de av og til også yte betydelig bedre enn andre måte å generere listene på. Syntaxen er og veldig enkel:
Kode
resultat = [verdi for verdi in mittSett if betingelse]
Kode
tall = [tall**2 for tall in xrange(100) if tall%2==1]
Kode
tall = {tall**2 for tall in xrange(100) if tall%2==1}
Kode
tall = [tall: tall**2 for tall in xrange(100) if tall%2==1]
Dictionarys - noen idiomer
En ting man ofte gjør i python er å bruke nøkkelene i dictionaryen. Når man skal sjekke om en nøkkel eksisterer i et dictinary er det enkelt å gjøre
Kode
dict.has_key(myKey)
Kode
key in dict
Kode
for key in dict: print key
Kode
for key in dict.keys(): print key
Men hva om du ønsker å interere over både verdien og nøkkelen samtidig. Den enkle måten å gjøre dette på blir da rett frem dette:
Kode
for key in dict: print key, "-->", dict[key]
Kode
for key, value in dict.items(): print key, "-->", value
Kode
for key, value in dict.iteritems(): print key, "-->", value
Settere og gettere i dictionary
Ta et program hvor du f.eks. ønsker å telle forekomsten av hver frukt i en fruktkurv, dette er noe som enkelt kan gjøres med følgende kode:
Kode
d = {} for frukt in fruktkurv: if frukt not in d: d[frukt] = 0 d[frukt] += 1
Kode
d = {} for frukt in fruktkurv: d[frukt] = d.get(frukt, 0) + 1
Kode
d = defaultdict(int) for frukt in fruktkurv: d[frukt] += 1
Håper et par av dere lærte noe nytt av dette, og fant noen trikt dere ikke var klar over. Om noe er uklart eller dere har noen spørsmål, kom med de i tråden her - så skal jeg (eller noen andre pythonfolk) gjøre vårt beste for å svare.
I den neste delen vil jeg ta for meg lambda-funksjoner, dekarator-funksjoner, bruk av gettere og settere i python og hvordan man kan lage funksjoner med variabelt antall argumenter.