Start | generator
 

Generatorer



En generator är en maskin som skapar någonting. T.ex. elektricitet. I python skapar generatorer t.ex. listor.

expr for item in iterable

Låt oss titta på nedanstående. Bekant.
for i in range(5): print(i)
Men vad gör de olika delarna här?
print(range(5))
Den lever sitt egna liv! Range är i själva verket ett objekt (ett iterator -objekt) som lämnar ifrån sig en serie tal (med startvärde och slutvärde och steg/step -värde), på begäran av något annat objekt. Range kan användas som samtalspartner till andra objekt t.ex. en lista.

T.ex. kommer range tillsammans med list att skapa en lista ...
print(list(range(5)))
Vilket leder tillbaka till loopen ovan. Om range kan hjälpa till att skapa en lista, så kanske man borde kunna banka in en lista direkt? Det kan man.
for i in [0,1,2,3,4]: print(i)
Vilket iofs inte är konstigt då vi lärt oss ...
lista = [0,1,2,3,4] for i in lista: print(i)
Men det finns andra automatiska sätt skapa t.ex. listor. Med generatorobjekt. Skrivsättet

x for x in range(5)

ger oss en generator. Vi kan skicka in denna generator i list() och då får vi en lista.
lista = list(x for x in range(5)) print(lista)
Vi kan skapa en lista utan att använda list(), genom att istället sätta hakparenteser runt generatoruttrycket.
lista = [x for x in range(5)] print(lista)
Verkar vettigt, gör det inte? Om man fyller hakparenteser med en serie tal - då har man ju en lista?

x for x in [ ... ]


Konceptet här, det är att för varje element i vår lista (eller annan manick som genererar ett flöde av siffror), så bygger vi upp en ny talserie.

Vi ges en möjlighet att göra något med elementet som plockats ut. Vi kan t.ex. ta det i kvadrat.

Eller konvertera det till en sträng.

Detta är ju mycket användbart om vi har en lista och vill konvertera till en sträng. Join som är väldigt kraftfull, tar tyvärr bara strängar. Men vi kan konvertera listans tal till strängar! Join har inget emot att hämta data från ett generatorobjekt.
lst = [1,2,4,5,6,4] string = " ".join(str(x) for x in lst) print(string)
Vi kan köra alla tal genom en helt egen funktion.

lst = [1,2,4,5,6,4] def dubbla(x): return(x*2) string = " ".join(str(dubbla(x)) for x in lst) print(string)
Vi kan också lägga till ett villkor på slutet.

Vi kan t.ex. kräva att talet ska vara udda (om x är udda får divisionen x/2 en rest, dvs x%2 (modulo) blir !=0 dvs True)
lst = [1,2,4,5,6,4] def dubbla(x): return(x*2) string = " ".join(str(dubbla(x)) for x in lst if x%2) print(string)

loop i en loop


Vi kan istället för if -satsen bygga vidare med for -loopar. Nedan kör en loop i en loop för att returnera en lista av tupler med koordinater.
lst= [(x,y) for x in range(5) for y in range(5)] print(lst)
lst= [(x,y,z) for x in range(3) for y in range(3) for z in range(3)] print(lst)

skapa dictionary


Det finns 2 sätt skapa en dictionary. Det ena är en generator som ger ifrån sig tupler och att använda dict.
d = dict((x,x) for x in range(5)) print(d)
Men det bästa sättet är att omge generatorn med { ... } och komma ihåg hur ett key : value ser ut med kolon emellan.
d = {n: n for n in range(5)} print(d)
En dictionary med alla dess tal faktoriserade, om det går.
def faktorisera(tal): lst = [] for d in range(2,1+tal//2): if(tal%d == 0): lst+=[d]+faktorisera(tal//d) return(lst) return [tal] def fakprim(n): lst = faktorisera(n) return("Primtal" if len(lst)<2 else lst) d = {n: fakprim(n) for n in range(1,100)} print(d)

JSON datafil


Vi kan importera en stor dictionary från en JSON-fil för att ha lite data att leka med. Studera datafilen här som är periodiska systemet (öppnas i nytt fönster).

Säg att vi från denna stora dictionary vill extrahera en lista av alla "element" (grundämnen) som är gas -formiga.
import json fil = open('/json/periodictable.json') per = json.load(fil) fil.close() gaser = ", ".join(e["name"] for e in per['elements'] if e["phase"] == 'Gas') print(gaser)
Listor vi skapar med generatorer kan vi också skicka in i de logiska funktionerna all() (alla element är sanna) och any() (någon element är sant)
import json fil = open('/json/periodictable.json') per = json.load(fil) fil.close() print("Alla ämnen har ett atomnummer:", end="") print(all(e["number"] >= 1 for e in per['elements'])) print("Alla ämnen har molar_heat:", end="") print(all(e["molar_heat"] !=None for e in per['elements'])) print("Lista ämnen molar_heat == None :") print(" ".join(str(e["name"]) for e in per['elements'] if e["molar_heat"]==None))
Vi kan låta en generator läsa vår dictionary och plocka ut lite information och skapa en ny dictionary. T.ex. en dictionary med alla ämnen som har ädelgas -struktur.
import json fil = open('/json/periodictable.json') per = json.load(fil) data = dict((e["name"],e["shells"]) for e in per["elements"] if e["shells"][-1]==8) print(data)



Lista av rader, modifiera varje rad


Säg att vi har en textfil vi önskar läsa in.
f = open('/csv/allcontries.csv', encoding="latin-1") lines = f.readlines() print(lines) f.close()
Vi ser att varje rad har en massa radslutstecken vi vill bli av med. Om indata är en lista, utdata en lista och vi ska göra något på varje element - typiskt uppgift för en generator!
f = open('/csv/allcontries.csv', encoding="latin-1") lines = [x.rstrip() for x in f.readlines()] print(lines) f.close()

tentafråga


Vad blir utdata? Det är en luring. Så vi har en generator som kommer skapa en serie av bokstäverna i 'typhoon'. Men runt generatorn sitter krullparenteser, dvs det är ett set (en mängd). I en mängd finns inga dubbletter och det finns inte heller någon ordning. Så dubbletter försvinner och ordningen kommer i praktiken bli bokstavsordning, vilket är samma sak som ingen ordning alls här.
a = {c for c in 'typhoon'} print(a)
Vad blir utdata? Ja, det blir en lista av bokstäver, eller hur?
a = [c for c in 'typhoon'] print(a)

initiera en tom 2d -lista


Många luringar på nätet om hur du ska initiera en 2d -lista i python. Rätt svar är nedan, du ska använda generatorer även för detta. En 10x10 lista...
lista2d = [[0]*10 for i in range(10)] lista2d[3][5] = 10 print(lista2d[3][5]) print(lista2d[4][6])

läs filer & generator -uttryck


I nedan exempel öppnar vi en fil och läser rad för rad. Fil nedan är ett itererbart objekt, därför kan vi använda det i en for -loop och resultatet är att vi läser en rad i taget och skapar en lista av rader. Vi kan sedan som vanligt lägga till andra saker i vårt generator, t.ex. rensa tecken eller annat, i nedan exempel så plockar rad.rstrip() bort nyradstecknet som avslutar varje rad.
with open('/ex/text.txt') as fil: rader = [rad.rstrip() for rad in fil] print(rader)

yield

Yield i denna kontext översätts kanske bäst med utbyte. När vi skriver yield i koden, så returneras värdet vid yield och sedan tar funktionen en paus och somnar. Dvs, där vi använde yield, exakt just den punkten där vi gjorde yield, där händer inget mer förrän den som använder funktionen gör next().

lat evaluering


Konceptet här - eller begreppet - kallas lat evaluering. Lat, därför att koden vi skapar kommer inte göra mer än nödvändigt i varje vända. Den tillhandahåller data när det behövs, annars inte. På så vis görs inget i onödan. Det kan verka lite omständigt när man tittar på små kodsnuttar, men i verkligheten kanske varje kalkyl eller inhämtning av data (över nätverk) tar lång tid. Då kan lat evaluering bli en jättestor besparing i tid. Exempelvis när du skrollar nedåt på en webbsida i mobiltelefonen, som aldrig tar slut. Här laddas sidan, lite då och då, över nätet, bara när det behövs.

def dum_generator(): for i in range(10): yield i tjoho = dum_generator() print(next(tjoho)) print(next(tjoho)) print(next(tjoho)) print(next(tjoho))

I nedan for -loop görs next() automatiskt i själva for -loopen.
def dum_generator(): yield 1 yield 2 yield 3 yield 4 yield 5 for i in dum_generator(): print(i)
Titta på nedan kod. I dum_generator() finns en loop. Vid första loopvarvet kommer den returnera yield 1 och sedan vänta, tills for -loopen genererar ett next() i sitt nästa varv.
def dum_generator(): for i in range(10): yield i for i in dum_generator(): print(i)
Vi printar ut lite för att se tydligare.
def dum_generator(): for i in range(10): print("dum_generator") yield i for i in dum_generator(): print("loopen") print(i)
Som du ser görs ju inte mer än nödvändigt, vilket också är generatorers största vinst. Vi får en lat evaluering, inte mer än nödvändigt görs. Effektivt, minneseffektivt.
def dum_generator(): for i in range(10): print("dum_generator") yield i lista = list(dum_generator())
Som du ser ovan, så slurpar list i sig alla tal dum_generator() ger ifrån sig och skapar en lista av det.

egen range


Vi kan skapa en egen range -funktion med yield. Så yield kommer nedan att, vid första frågan om ett tal, ge ifrån sig 0. Nästa gång vi väcker funktionen genom att fråga efter ett tal, så lämnar den ifrån sig 1.
def minRange(t): num = 0 while num < t: yield num num += 1 for i in minRange(10): print(i)
Vi kan skapa listor, mängder (set) och annat med vår egen generatorfunktion.
def minRange(t): num = 0 while num < t: yield num num += 1 l = list(minRange(5)) print(l) s = set(minRange(5)) print(s)
Vill vi lägga till start, stop och step i vår egen range -funktion så gör vi det. Vi trixar lite med standardvärden så att vi kan anropa med 1, 2 eller 3 argument.
def minRange(f,t=None,step=1): if not t: t = f f = 0 num = f if step<1 or num >= t: raise Exception("Blir ju en evighetsloop ju!!!") while num < t: yield num num +=step for i in minRange(2,8,2): print(i)
Säg att vi vill få en serie av tal som är faktorer av något...
def faktorisera(f): d = 2 while d < f: if(f%d == 0): yield d f = f//d d = 2 else: d += 1 yield f for i in faktorisera(256): print(i, end=",")
Eller säg att vi vill få en serie av tal som flippar 10,0,9,1,8,2,7,3, ...
def flipRange(n): l,h = 0,n while l <= h: yield l if h != l: yield h l += 1 h -= 1 for i in flipRange(10): print(i, end=",")

Lite frågor

Vad blir genererar list(x%2 for x in range(5))
  [1, 1, 1, 1, 1]
  [1, 0, 1, 0, 1]
  [0, 1, 0, 1, 0]
  [0, 0, 0, 0, 0]
Vad blir genererar list(x for x in range(10) if x%2)
  [2, 4, 6, 8, 10]
  [1, 3, 5, 7, 9]
  [0, 2, 4, 6, 8]
  [1, 2, 3, 4, 5]
Vad blir genererar list(x for x in [1,2,4,5,6,4] if x%2)
  [2, 4, 6, 4]
  [1, 5]
  [2, 4, 5, 6, 4]
Vad blir genererar list(x**2 for x in [1,3,4,5,6,4] if x%3==0)
  [1, 16, 25, 16]
  [1, 9, 16, 25, 36, 16]
  [9, 36]
print(len({n==n for n in range (1,5)})
Begrunda ovan luriga tentafråga. Vad blir utskriften?
  4
  5
  1

övningar

■ Skriv en generator som ger en lista med tal 0 till 10.
  Visa lösning
■ Skriv en generator som ger en lista av kvadrerade tal (x^2)
  Visa lösning
■ Skriv en generator som ger en lista med nummer mellan 1 och 10 som är udda
  Visa lösning
■ Skriv en generator som ger en lista med nummer mellan 1 och 10 som är jämna
  Visa lösning
■ Skriv en generator som ger en lista av talen 1,10,100,1000,1000 upp till 9 nollor.
  Visa lösning
■ Skriv en generator som ger en lista av talen 0-10 konverterade till strängar
  Visa lösning
■ Skriv en generator som ger en lista av udda tal 0-10 konverterade till strängar
  Visa lösning
■ Skriv en egen funktion dubbla som dubblar det du skickar in
  Visa lösning
■ Skriv en generator som ger tal 0-10 som dubblar varje tal med funktionen du nyss skrev.
  Visa lösning
■ Join kan bygga ihop en sträng av en lista. Skriv en generator som ger en lista av tal 0-10, som sedan joinar ihop allt till en sträng
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en lista av 0 och 1 beroende på om talet är udda eller inte
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en lista av 1 och 2 beroende på om talet är udda eller inte
  Visa lösning
■ Skriv en generator som ger tal 0-10 men ger utdata en lista av texterna "udda" och "jämn" beroende på om talet jämnt eller udda
  Visa lösning
■ Skriv en generator som ger tal 0-10 men ger utdata en lista av texterna "delbart med 3" och "inte delbart med 3" beroende på om talet är delbart med 3
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en lista av tupler av talet ex [(1,1), (2,2), (3,3), ... ]
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en lista av tupler där ena halvan av tupeln är 0 eller 1 beroende på om det är udda eller jämt [(1,1), (2,0), (3,1), (4,0),... ]
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en dictionary där key är siffran och value är 0 eller 1 beroende på om talet är udda eller jämt. {1:1, 2:0, 3:1, 4:0}. Tips, du tänker som om du ska skriva en tupel (key, value). Tips, istället för att konvertera till lista () använder du dict()
  Visa lösning
■ Skriv en generator som skapar tal 0-10 men som ger ifrån sig en dictionary där key är siffran och value är en sträng "udda" eller "jämt" beroende på om talet är udda eller jämt.
  Visa lösning
20.940065383911 ms