Osa 11

Mer om comprehensions

Listor är kanske det vanligaste målet för comprehensions, men comprehensions fungerar på alla serier av föremål, inklusive strängar. Liksom listexemplen i föregående avsnitt, ifall en list comprehension utförs på en sträng, plockas föremålen (dvs. tecknen) i strängen en efter en, bearbetas enligt det givna uttrycket och lagras i en lista.


namn = "Peter Python"

stora_bokstaver = [tecken.upper() for tecken in namn]
print(stora_bokstaver)
Exempelutskrift

['P', 'E', 'T', 'E', 'R', ' ', 'P', 'Y', 'T', 'H', 'O', 'N']

Resultatet är en lista, vilket dikteras av parentesnotationen runt comprehension-satsen. Om vi ville ha en sträng istället skulle vi kunna använda strängmetoden join för att tolka listan till en sträng. Kom ihåg att metoden anropas på den sträng som vi vill använda som "lim" mellan tecknen. Låt oss ta en titt på några exempel:


namn = "Peter"
lista = list(namn)
print(lista)

print("".join(lista))
print(" ".join(lista))
print(",".join(lista))
print(" och ".join(lista))
Exempelutskrift

['P', 'e', 't', 'e', 'r'] Peter P e t e r P,e,t,e,r P och e och t och e och r

List comprehensions och join-metoden gör det enkelt att skapa nya strängar baserade på andra strängar. Vi kan t.ex. skapa en sträng som bara innehåller vokalerna från en annan sträng:


teststrang = "Halloj allihopa, det här är ett test"

vokaler = [tecken for tecken in teststrang if tecken in "aeiouyåäö"]
nystrang = "".join(vokaler)

print(nystrang)
Exempelutskrift

aoaioaeääee

I exemplet ovan står list comprehension och join-metoden på separata rader, men de kan kombineras till ett enda uttryck:


teststrang = "Halloj allihopa, det här är ett test"

vokalstrang = "".join([tecken for tecken in teststrang if tecken in "aeiouyåäö"])

print(vokalstrang)

Många Python-programmerare står trogna vid dessa oneliners, så det är väl värt besväret att lära sig läsa dem. Vi kan till och med lägga till split-metoden i mixen, så att vi kan bearbeta hela meningar effektivt med ett enda uttalande. I exemplet nedan tas det första tecknet från varje ord i en mening bort:


mening = "Sju sjösjuka sjömän på skeppet Shang Hai."

mening_utan_initialer = " ".join([ord[1:] for ord in mening.split()])
print(mening_utan_initialer)
Exempelutskrift

ju jösjuka jömän å keppet hang ai

Låt oss gå igenom detta steg för steg:

  • ord[1:] extraherar en delsträng från det andra tecknet (vid index 1) och framåt
  • mening.split() delar upp meningen i avsnitt vid det angivna tecknet. I det här fallet ges inget argument till metoden, så meningen delas upp vid mellanslagstecken som standard
  • " ".join() kombinerar föremålen i listan till en ny sträng med ett mellanslag mellan föremålen

En mer traditionell iterativ metod skulle kunna se ut så här:


mening = "Sju sjösjuka sjömän på skeppet Shang Hai."

ordlista = []
orden = mening.split()
for ord in orden:
    ord_utan_initialer = ord[1:]
    ordlista.append(ord_utan_initialer)

mening_utan_initialer = " ".join(ordlista)


print(mening_utan_initialer)
Loading

Egna klasser och comprehensions

Comprehensions kan vara ett användbart verktyg för att bearbeta eller formulera instanser av dina egna klasser, vilket vi kommer att se i följande exempel.

Låt oss först ta en titt på klassen Land som är en enkel modell för ett enda land, med attribut för namn och befolkning. I huvudfunktionen nedan skapar vi först några Land-objekt och använder sedan en list comprehension för att bara välja dem vars befolkning är större än fem miljoner.


class Land:
    """ Denna klass modellerar ett enkelt land med befolkning """
    def __init__(self, namn: str, befolkningsmangd: int):
        self.namn = namn
        self.befolkningsmangd = befolkningsmangd

if __name__ == "__main__":
    finland = Land("Finland", 6000000)
    malta = Land("Malta", 500000)
    sverige = Land("Sverige", 10000000)
    island = Land("Island", 350000)

    lander = [finland, malta, sverige, island]

    storre_land = [land.namn for land in lander if land.befolkningsmangd > 5000000]
    for land in storre_land:
        print(land)

Exempelutskrift

Finland Sverige

I list comprehension ovan valde vi bara namnattributet från Land-objekten, så innehållet i listan kunde skrivas ut direkt. Vi skulle också kunna skapa en ny lista med länderna och komma åt namnattributet i for-loopen. Detta skulle vara användbart om samma lista med länder skulle användas senare i programmet, eller om vi behövde befolkningsattributet i for-loopen också:


if __name__ == "__main__":
    finland = Land("Finland", 6000000)
    malta = Land("Malta", 500000)
    sverige = Land("Sverige", 10000000)
    island = Land("Island", 350000)

    lander = [finland, malta, sverige, island]

    storre_land = [land for land in lander if land.befolkningsmangd > 5000000]
    for land in storre_land:
        print(land.namn)

I nästa exempel har vi en klass som heter Fotlopp som modellerar ett enskilt lopp med attribut för loppets längd och namn. Vi kommer att använda list comprehension för att skapa Fotlopp-objekt baserat på en lista med tävlingslängder.

Parametern namn har ett standardvärde i konstruktorn för Fotlopp-klassen, vilket är varför vi inte behöver skicka namnet som ett argument.


class Fotlopp:
    """ Klassen modellerar ett fotloppsevenemang med längden n meter """
    def __init__(self, stracka:int, namn:str = "inget namn"):
        self.stracka = stracka
        self.namn = namn

    def __repr__(self):
        return f"{self.stracka} m. ({self.namn})"

if __name__ == "__main__":
    langder = [100, 200, 1500, 3000, 42195]
    strackor = [Fotlopp(langd) for langd in langder]

    # Skriv ut alla
    print(strackor)

    # Ta en från listan och ge den ett namn
    maraton = strackor[-1] # sista föremålet i listan
    maraton.namn = "Maraton"

    # Skriv ut alla igen, inkluderandes det nya namnet
    print(strackor)
Exempelutskrift

[100 m. (inget namn), 200 m. (inget namn), 1500 m. (inget namn), 3000 m. (inget namn), 42195 m. (inget namn)] [100 m. (inget namn), 200 m. (inget namn), 1500 m. (inget namn), 3000 m. (inget namn), 42195 m. (Maraton)]

Låt oss nu ta reda på vad som gör en serie objekt "begripliga" (“comprehendible”). I föregående del lärde vi oss hur vi kan göra våra egna klasser itererbara. Det är exakt samma funktion som också möjliggör list comprehension. Om din egen klass är itererbar kan den användas som grund för en list comprehension. Följande klassdefinitioner är kopierade direkt från modul 10:


class Bok:
    def __init__(self, namn: str, forfattare: str, sidor: int):
        self.namn = namn
        self.forfattare = forfattare
        self.sidor = sidor

class Bokhylla:
    def __init__(self):
        self._bocker = []

    def tillsatt_bok(self, bok: Bok):
        self._bocker.append(bok)

    # Iteratorns initialiseringsmetod
    # Här bör iterationsvariabeln(variablerna) initialiseras
    def __iter__(self):
        self.n = 0
        # Metoden returnerar en referens till själva objektet
        # eftersom iteratorn är implementerad inom samma klassdefinition
        return self

    # Denna metod returnerar nästa föremål inom objektet
    # Om alla föremål har genomgåtts åstadkomms StopIteration
    def __next__(self):
        if self.n < len(self._bocker):
            # Ta det aktuella föremålet från listan i objektet
            bok = self._bocker[self.n]
            # Öka räknaren med ett
            self.n += 1
            # ...och returnera föremålet
            return bok
        else:
            # Inga fler böcker
            raise StopIteration

# Testar
if __name__ == "__main__":
    b1 = Bok("Livet av en Python", "Peter Python", 123)
    b2 = Bok("Den gamle och Java", "Ernest Hemingjava", 204)
    b3 = Bok("C-värdheter på nätet", "Karl Kodare", 997)

    hylla = Bokhylla()
    hylla.tillsatt_bok(b1)
    hylla.tillsatt_bok(b2)
    hylla.tillsatt_bok(b3)

    # Skapa en lista innehållandes namnet på alla böcker
    bockernas_namn = [bok.namn for bok in hylla]
    print(bockernas_namn)
Loading
Loading

Comprehensions och ordlistor

Det finns inget i sig "list-aktigt" med comprehensions. Resultatet är en lista eftersom comprehension-satsen är inkapslad i hakparenteser, som indikerar en Python-lista. Förståelser fungerar lika bra med Python-ordlistor om du använder rundparenteser istället. Kom dock ihåg att ordlistor kräver nyckel-värde-par. Båda måste anges när en ordlista skapas, även när det gäller comprehensions.

Grunden för en comprehension kan vara vilken itererbar serie som helst, vare sig det är en lista, en sträng, en tupel, en ordlista, någon av dina egna itererbara klasser och så vidare.

I följande exempel använder vi en sträng som bas för en ordlista. Ordlistan innehåller alla unika tecken i strängen, tillsammans med antalet gånger de förekommer:


mening = "Hej alla"

tecken_antal = {bokstav : mening.count(bokstav) for bokstav in mening}
print(tecken_antal)
Exempelutskrift

{'H': 1, 'e': 1, 'j': 1, ' ': 1, 'a': 2, 'l': 2}

Principen för comprehension-satsen är exakt densamma som för listor, men i stället för ett enda värde består uttrycket nu av en nyckel och ett värde. Den allmänna syntaxen ser ut så här:

{<nyckeluttryck> : <värdeuttryck> för <föremål> i <serie>}

Som avslutning på det här avsnittet tittar vi på faktorialtal igen. Den här gången lagrar vi resultaten i en ordlista. Själva talet är nyckeln, medan värdet är resultatet av faktorn från vår funktion:


def fakultet(n: int):
    """ Funktionen beräknar fakulteten n! för positiva heltal """
    k = 1
    while n >= 2:
        k *= n
        n -= 1
    return k

if __name__ == "__main__":
    lista = [-2, 3, 2, 1, 4, -10, 5, 1, 6]
    fakultett = {tal : fakultet(tal) for tal in lista if tal > 0}
    print(fakulteter)
Exempelutskrift

{3: 6, 2: 2, 1: 1, 4: 24, 5: 120, 6: 720}

Loading
Loading
Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.