Du må være registrert og logget inn for å kunne legge ut innlegg på freak.no
X
LOGG INN
... eller du kan registrere deg nå
Dette nettstedet er avhengig av annonseinntekter for å holde driften og videre utvikling igang. Vi liker ikke reklame heller, men alternativene er ikke mange. Vær snill å vurder å slå av annonseblokkering, eller å abonnere på en reklamefri utgave av nettstedet.
  10 3920
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:

Kode

for x in range(5):
	print x
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

Kode

for x in [0, 1, 2, 3, 4]:
	print x
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.

Kode

for x in xrange(5):
	print x
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.

Kode

frukter = ["eple", "banan", "pære"]
for i in range(len(frukter)):
	print frukter[i]
Dette er veldig feil måte å gjøre ting på i python, i stede vil man heller gjøre dette:

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]
Dette er akkurat samme feil som før, man skal ikke bruke index. Derfor gjør man heller dette:

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]
Dette er feil måte å gjøre det på, i stede skal man bruke funksjonen "enumerate":

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]
I slike tilfeller har man er funksjon som heter zip, fom komvinerer elementene fra de ulike listene til tupler. Dette gir mye finere kode.

Kode

for frukt, farge in zip(frukter, farger):
	print frukt, farge
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.

Kode

for frukt, farge in izip(frukter, farger):
	print frukt, farge
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).

Kode

blocks = []
while True:
	block = f.read(32)
	if block == "":
		break
	blocks.append(block)
print "".join(blocks)
Dette kan man gjøre mye finere med en for-løkke, og man kan da bruke noe lignende dette:

Kode

blocks = []
for block in iter(partial(f.read, 32), ""):
	blocks.append(block)
print "".join(blocks)
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:

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]
Dette kan man bruke til å f.eks. lage en liste med kvadratene av alle oddetall under 100

Kode

tall = [tall**2 for tall in xrange(100) if tall%2==1]
Dette kan også gjøres for både sett og dictionarys i python. For å lage et sett bruke man krøllparanteser i stede.

Kode

tall = {tall**2 for tall in xrange(100) if tall%2==1}
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.

Kode

tall = [tall: tall**2 for tall in xrange(100) if tall%2==1]
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

Kode

dict.has_key(myKey)
men dette er feil måte i python, i stede bruk IN

Kode

key in dict
Det samme gjelder når man ønsker å iterere over nøkkelene, da skal du gjøre dette

Kode

for key in dict:
	print key
ikke dette:

Kode

for key in dict.keys():
	print key
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:

Kode

for key in dict:
	print key, "-->", dict[key]
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:

Kode

for key, value in dict.items():
	print key, "-->", value
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:

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

Kode

d = {}
for frukt in fruktkurv:
	d[frukt] = d.get(frukt, 0) + 1
Dette kan også forbedres videre med å bruke default-dict i stede for en vanlig dictionary:

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.
Liker guiden din, flere småtips jeg ikke hadde fått med meg (izip, defaultdict og with). Fortsett gjerne.

Forøvrig, til alle andre: så er det Etse her skriver absolutt helt essensielle vaner om man skal holde på med store datasett (+100000 items). Da blir nemlig å lage unødvendige midlertidige kopier av dataene dine faktisk en veldig kostbar operasjon, noe som gjør at å konsekvent bruke iterators gir automatisk rask og rimelig effektiv kode.

Har selv gått over andres kode og redusert kjøretid fra +2 min til 10-15 sekunder bare ved å kjapt gå igjennom og fjerne unødvendig instansiering. I praksis er faktisk det å velge riktig algoritme og fjerne idiotisk ressursbruk særdeles mye mer nyttig enn å lære seg å skvise de siste klokkesyklene ut av datamaskinen når det gjelder å få algoritmer til å gå fort.
Ukjent
Trådstarter Donor
Det DumDiDum sier her er veldig riktig, men du trenger ikke gå så langt som 100.000 elementer i et datasett før forskjellene blir merkbare - Du må huske på at CPU-cachen er gjerne veldig liten så det skal ikke så mye data til før man risikerer vesentlige mengder med cache-miss, og på intel-arkitekturen betyr det at en memory-read kan ta opp til like lang tid som en floating-point-division. Med andre ord, i stede for en halv sykel til cirka 10 sykler. Så forskjellen blir stor.

Og poenget er at det er rett og slett ingen grunn til å ikke bruke iteratorer, det er python måten å gjøre det på - så man burde bare gjøre det alltid. Selv om settet bare er 4-5 elementer og du ville tjent nesten ingenting på det, så taper du heller ingenting. Og det handler bare om å legge til seg gode vaner. (det er noe som er sant for det meste innenfor programmering, bare legg til deg gode vaner med en gang)

Og takk for positiv tilbakemelding, skal skrive del 3 så fort jeg får litt tid. Blir vel om et par uker tenker jeg.
Sist endret av etse; 1. august 2013 kl. 16:18.
Flott tiltak! Et spørsmål: hvis range(5) lager tallene 0 til 5, hvorfor har motparten under bare tallene 0 til 4?
Ukjent
Trådstarter Donor
Sitat av fuzzy76 Vis innlegg
Flott tiltak! Et spørsmål: hvis range(5) lager tallene 0 til 5, hvorfor har motparten under bare tallene 0 til 4?
Vis hele sitatet...
jeg skrev feil. Det skal være 5 tall, fra 0 til og med 4. Slurvefeil fra min side Så range(5) == [0, 1, 2, 3, 4]
Sist endret av etse; 2. august 2013 kl. 09:49.
Veldig fin guide Du forklarer på en god og oversiktlig måte. Jeg har brukt python ganske mye tidligere, men alt for lite nå til dags. Python er jo et supert språk som absolutt flere folk burde få øynene opp for. Ikke bare er rein python bra, men se på gode rammeverk for web som Django og spillrammeverk som http://www.pygame.org.

KP!
Ukjent
Trådstarter Donor
Vil og oppfordre andre, hvis dere har kunnskaper innenfor ulike programmeringsfelt, til å skrive noen enkle guider. Men sett av litt tid å gjør det ordentlig, og beregn at det vil nok ta minst 4-5 timer å skrive noe på lengden med det jeg har her.
▼ ... over en uke senere ... ▼
Synes du burde ta med litt forskjeller fra Python 2 og 3
Python2

Kode

>>> d = {"a": "Hello", "b": "World"}
>>> d.items()
[('a', 'Hello'), ('b', 'World')]
>>> d.values()[0]
'Hello'
Python3

Kode

>>> d = {"a": "Hello", "b": "World"}
>>> d.values()
dict_values(['Hello', 'World'])
>>> d.values()[0] # Waitwhat?
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'dict_values' object does not support indexing
>>> list(d.values())[0] #Phew
'Hello'
I Python3 er .iteritems() det samme som items().
Ukjent
Trådstarter Donor
jeg har egentlig aldri brukt Python3, og jeg liker veldig dårlig å prøve å forklare ting til andre hvis jeg ikke har veldig god forståelse og erfaring rundt det jeg forklarer. - men prøver heller å ta for meg idiomer og tenker også det kan bli forstyrrende for innholdet om jeg også skal si forskjellet mellom python 2 og 3 - for teorien rundt det er det samme.

Python 3 skal uansett ha som positivt at mange av "settene" har blir byttet ut med "iterators" som default - noe som er en god ting
▼ ... noen måneder senere ... ▼
Fantastisk flott tiltak, og veldig godt og oversiktelig skrevet, tusen takk! Jeg er veldig spent på del 3, lambda er noe som er helt kryptisk for meg
Bra dette este,jeg kan fylle på litt.

Vil også nevne Raymond Hettinger som skal ha en god del av æren for at Python har utviklet seg veldig posestivt etter år 2000.
For og nevne noe han har kommet med enumerate(),reverse(),set(),sorted(),namedtuple()
og moduler som itertools og collections

På slutten av posten til este brukes det dictionary og collections.defaultdict til telle forekomster.
Telle er noe man gjør mye av i programmering.
Ser på collections.Counter som gjør dette mesterlig.

Fruktkurv med collections.Counter

Kode

>>> from collections import Counter
>>> fruitbasket = ['Apple', 'Apple', 'Pear', 'Orange', 'Banana', 'Apple', 'Banana']
>>> fruit_count = Counter(fruitbasket)
>>> fruit_count
Counter({'Apple': 3, 'Banana': 2, 'Orange': 1, 'Pear': 1})

>>> fruit_count.most_common(1)
[('Apple', 3)]
>>> fruit_count.most_common(2)
[('Apple', 3), ('Banana', 2)]

>>> fruit_count['Apple']
3
>>> fruit_count['Banana']
2
To fruktkurver med collections.Counter og itertools

Kode

two_fruitbasket = [['Apple','Apple','Banana'], ['Pear','Apple','Banana']]
Hvordan løse dette loope over hver kurv og gi resualt til Counter() og så slå sammen?
Nei her kan enn mikse inn itertool.chain,de fine med disse modulene er at dem fungere veldig bra sammen.

Kode

>>> import itertools
>>> from collections import Counter
>>> two_fruitbasket = [['Apple','Apple','Banana'], ['Pear','Apple','Banana']]
>>> nested_counter = Counter(itertools.chain(*two_fruitbasket))
>>> nested_counter
Counter({'Apple': 3, 'Banana': 2, 'Pear': 1})

>>> nested_counter.most_common(1)
[('Apple', 3)]
>>> nested_counter['Pear']
1
>>>
Til slutt med litt gøy collections.Counter,laster ned eventyr boken Bibelen.
Fjerner alt tegnsetting(med regex),teksten blir splittet opp til en liste,
og man konverter til små bokstaver.
Ikke så ille for 10 linjer med kode.

Bibelen med collections.Counter

Kode

import re
from collections import Counter
from urllib import urlretrieve

url = 'http://www.gutenberg.org/cache/epub/10/pg10.txt'
filename = url.split('/')[-1]
urlretrieve(url, filename)
with open(filename) as f:
    clean_text = re.findall(r'\w+', f.read().lower())
bible_counter = Counter(clean_text)
Bruke denne koden,spenning hva er det mest vanlige ordet i Bibelen?

Kode

>>> bible_counter.most_common(1)
[('the', 64204)]
Ikke så spennende "the" forekommer mest med 64204 ganger i Bibelen.
De 15 mest vanlige ordene.

Kode

[
>>> bible_counter.most_common(15)
('the', 64204),
 ('and', 51764),
 ('of', 34789),
 ('to', 13660),
 ('that', 12927),
 ('in', 12725),
 ('he', 10422),
 ('shall', 9840),
 ('for', 8997),
 ('unto', 8997),
 ('i', 8854),
 ('his', 8473),
 ('a', 8235),
 ('lord', 7964),
 ('they', 7379)]
Hvor mange ganger forekommer ord som god,jesus,heaven,hell?

Kode

>>> names = ['god','jesus','heaven','hell']
>>> result = [bible_counter[i] for i in names]
>>> result 
[4472, 983, 583, 54]
"god" vinner med 4472 forekommster,og skuffende for "hell" med bare 54.


Kan lage et eget dictionary av de to listene names og result.

Kode

# Make dictionary of the two list with mix in zip
>>> name_dict = dict(zip(names, result))
>>> name_dict
{'god': 4472, 'heaven': 583, 'hell': 54, 'jesus': 983}
>>> name_dict['hell']
54
Lengste ord i Bibelen?

Kode

>>> max(clean_text, key=len)
'mahershalalhashbaz'
Sånn kunne enn fortsatt og tatt ut all mulig statistikk om ord i Bibelen.
Håper dette var litt greit påfyll,viss ikke bare si ifra.