Hur använder man uppackningsoperatorerna (*, **) i Python?

By rik

Python, ett av de mest omtyckta programmeringsspråken, döljer många praktiska finesser. En av dessa, ofta förbisedd men mycket kraftfull, är uppackning. Låt oss utforska hur det fungerar!

Du har säkert stött på symbolerna * och ** i kod. Kanske har du till och med använt dem utan att fullt förstå deras funktion. Denna genomgång kommer att klargöra konceptet uppackning och visa hur du kan använda det för att skriva mer elegant och ”pythonisk” kod.

Här är en lista över termer som är bra att känna till under läsningen:

  • Iterable: En samling som kan itereras med en for-loop. Exempel är listor, tupler, set och dictionaries.
  • Callable: Ett Python-objekt som kan anropas med parenteser (), som en funktion (t.ex. min_funktion()).
  • Shell: En interaktiv miljö där vi kan köra Python-kod direkt. Du startar den genom att skriva python i en terminal.
  • Variabel: Ett symboliskt namn som refererar till en plats i minnet där ett objekt lagras.

Först, låt oss adressera en vanlig förväxling: asterisker används också som aritmetiska operatorer. En asterisk (*) anger multiplikation, medan två asterisker (**) betyder exponentiering.

>>> 3*3
9
>>> 3**3
27

Detta kan du verifiera genom att öppna ett Python-shell och skriva in uttrycken.

Obs! För att följa denna handledning behöver du ha Python 3 installerat. Om du inte har det kan du kolla in vår guide för hur man installerar Python.

Här ser vi alltså hur asterisken placeras mellan siffrorna för att utföra aritmetiska operationer.

>>> *range(1, 6),
(1, 2, 3, 4, 5)
>>> {**{'vanilj':3, 'choklad':2}, 'jordgubb':2}
{'vanilj': 3, 'choklad': 2, 'jordgubb': 2}

Men asterisker (*, **) används också *framför* en iterable. Då används de för att packa upp den, som du ser i exemplet ovan.

Oroa dig inte om detta känns förvirrande – det är bara en introduktion till begreppet uppackning i Python. Läs vidare så klarnar det!

Vad betyder ”uppackning”?

Uppackning är processen att extrahera värden från iterables, till exempel listor, tupler och dictionaries. Tänk dig att du öppnar en låda och plockar ut enskilda saker – det är ungefär så uppackning fungerar.

Uppackning i Python är alltså analogt med att packa upp en låda i verkligheten.

>>> min_låda = ['kablar', 'hörlurar', 'USB']
>>> sak1, sak2, sak3 = min_låda

Låt oss illustrera detta med kod:

Som du ser tilldelar vi de tre elementen från listan min_låda till tre separata variabler: sak1, sak2 och sak3. Det här är själva grunden i hur uppackning fungerar i Python.

>>> sak1
'kablar'
>>> sak2
'hörlurar'
>>> sak3
'USB'

Om vi kontrollerar värdet av varje variabel ser vi att sak1 refererar till ”kablar”, sak2 till ”hörlurar” och så vidare.

>>> ny_låda = ['kablar', 'hörlurar', 'USB', 'mus']
>>> sak1, sak2, sak3 = ny_låda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)

Hittills verkar det ganska enkelt. Men tänk om vi försöker packa upp en lista som innehåller *fler* element än antalet variabler som vi försöker tilldela dem till?

Säkert nog får vi ett felmeddelande. Vi försöker alltså att tilldela 4 värden till 3 variabler. Hur ska Python lyckas med det? Det går inte, och det är därför vi får ett ValueError

med meddelandet ”för många värden att packa upp”. Felet uppstår alltså för att vi har tre variabler till vänster om likhetstecknet, men fyra värden (elementen i listan ny_låda) till höger.

>>> sista_låda = ['kablar', 'hörlurar']
>>> sak1, sak2, sak3 = sista_låda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

Om vi istället försöker packa upp *färre* värden än antalet variabler får vi också ett ValueError, fast med ett lite annat felmeddelande:

Obs! Vi har nu fokuserat på listor, men samma uppackningssätt fungerar för alla iterables (listor, set, tupler, dictionaries).

Hur hanterar vi då situationer som dessa? Finns det ett sätt att packa upp alla element i en iterable till ett mindre antal variabler utan att få fel?

Javisst! Lösningen är uppackningsoperatorn (* och **). Låt oss se hur de fungerar i Python.

Hur man packar upp listor med *-operatorn

Asteriskoperatorn

>>> första, *oanvända, sista = [1, 2, 3, 5, 7]
>>> första
1
>>> sista
7
>>> oanvända
[2, 3, 5]

används för att packa upp alla värden i en iterable som inte redan har tilldelats en variabel.

>>> första, *_, sista = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

Anta att du vill få tag på det första och sista elementet i en lista, utan att använda index. Då kan du använda asteriskoperatorn så här:

>>> första, *_, sista = [1, 2]
>>> första
1
>>> sista
2
>>> _
[]

Som du ser, fångar asteriskoperatorn upp alla ”oanvända” värden. Det är en god sed att använda en understrecksvariabel (_) för att indikera att värdena inte kommer att användas, en så kallad ”dummy-variabel”.

Tricket funkar även om listan bara har två element:

I detta fall blir understrecksvariabeln (dummyvariabeln) en tom lista. Det gör att de två övriga variablerna kan tilldelas de tillgängliga värdena i listan.

>>> *sträng = 'PythonÄrBäst'

Vanliga misstag

>>> *sträng = 'PythonÄrBäst'
  File "<stdin>", line 1
SyntaxError: starred assignment target must be in a list or tuple

Vi kan packa upp varje element i en iterable. Det kan leda till något som detta: Men koden ovan returnerar ett SyntaxError. Det beror på att enligt

PEP-specifikationen:

>>> *sträng, = 'PythonÄrBäst'
>>> sträng
['P', 'y', 't', 'h', 'o', 'n', 'Ä', 'r', 'B', 'ä', 's', 't']

måste det finnas en tuple (eller lista) på vänster sida av tilldelningen.

>>> *nummer, = range(5)
>>> nummer
[0, 1, 2, 3, 4]

Om vi vill packa upp varje värde i en iterable till en *enda* variabel behöver vi alltså skapa en tuple (eller lista). Det gör vi enkelt genom att lägga till ett kommatecken.

Ett annat exempel är användningen av funktionen range, som returnerar en sekvens av tal.

Nu när du vet hur du kan packa upp listor och tupler med en enkel asterisk (*), är det dags att lära sig hur man packar upp dictionaries.

Hur man packar upp dictionaries med **-operatorn

>>> **hälsningar, = {'hej': 'HEJ', 'hejdå':'HEJDÅ'} 
...
SyntaxError: invalid syntax

En enkel asterisk (*) används för att packa upp listor och tupler. Den dubbla asterisken (**) används däremot för dictionaries.

>>> mat = {'fisk':3, 'kött':5, 'pasta':9} 
>>> färger = {'röd': 'intensitet', 'gul':'glädje'}
>>> sammanslagen_dict = {**mat, **färger}
>>> sammanslagen_dict
{'fisk': 3, 'kött': 5, 'pasta': 9, 'röd': 'intensitet', 'gul': 'glädje'}

Tyvärr kan vi inte packa upp en dictionary till en enda variabel på samma sätt som vi gjorde med listor och tupler. Följande ger alltså ett fel:

Däremot kan vi använda **-operatorn inuti ”callables” (funktioner) och andra dictionaries. Om vi t.ex. vill skapa en ny dictionary som är en sammanslagning av andra dictionaries kan vi göra så här:

Detta är ett ganska praktiskt sätt att slå ihop dictionaries, men det är inte det huvudsakliga syftet med uppackning i Python.

Låt oss nu se hur vi kan använda uppackning i samband med callables.

Uppackning i funktioner: args och kwargs

Du har säkert sett ”args” och ”kwargs” tidigare, antingen i klasser eller i funktioner. Låt oss se varför de är viktiga när vi jobbar med ”callables”.

>>> def produkt(n1, n2):
...     return n1 * n2
... 
>>> tal = [12, 1]
>>> produkt(*tal)
12

Uppackning med *-operatorn (args)

>>> produkt(12, 1)
12

Anta att vi har en funktion som beräknar produkten av två tal.

>>> tal = [12, 1, 3, 4]
>>> produkt(*tal)
...
TypeError: product() takes 2 positional arguments but 4 were given

Som du ser packar vi upp listan tal och skickar värdena till funktionen. Detta är alltså exakt samma som att skriva:

>>> def produkt(*args):
...     resultat = 1
...     for i in args:
...             resultat *= i
...     return resultat
...
>>> produkt(*tal)
144

Hittills fungerar allt fint. Men om vi vill skicka en *längre* lista då? Det skapar ett fel eftersom funktionen tar emot fler argument än den är byggd för.

Vi kan lösa det genom att ”packa upp” listan *i själva funktionsanropet*. Då skapas en iterable i funktionen, som gör det möjligt att skicka ett valfritt antal argument till den.

Här behandlar vi parametern args som en iterable. Vi går igenom varje element i den och returnerar produkten av alla tal. Observera att det initiala värdet på resultat måste vara 1. Om vi börjar med 0 kommer funktionen alltid att returnera 0. Obs! ”args” är bara ett namn som används av konvention. Du kan använda vilket annat parameternamn som helst. Vi kan även skicka godtyckliga tal till funktionen utan att först skapa en lista, ungefär som med den inbyggda funktionen

>>> produkt(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

print().

>>> def testa_typ(*args):
...     print(type(args))
...     print(args)
... 
>>> testa_typ(1, 2, 4, 'en sträng')
<class 'tuple'>
(1, 2, 4, 'en sträng')

.

Låt oss slutligen ta reda på vilken datatyp args har inuti funktionen.

Som du ser i koden ovan är args alltid en tuple. Innehållet är de positionsbundna argumenten som skickas till funktionen.

Uppackning med **-operatorn (kwargs)

>>> def skapa_person(namn, **kwargs):
...     resultat = namn + ': '
...     for nyckel, värde in kwargs.items():
...             resultat += f'{nyckel} = {värde}, '
...     return resultat
... 
>>> skapa_person('Melissa', id=12112, plats='london', tillgångar=12000)
'Melissa: id = 12112, plats = london, tillgångar = 12000, '

Som vi såg tidigare används operatorn ** uteslutande för dictionaries. Det innebär att vi med denna operator kan skicka nyckel-värde-par till funktionen som argument.

Låt oss skapa en funktion skapa_person, som tar ett positionsbundet argument ”namn” och ett obestämt antal nyckelordsbundna argument.

Som du ser konverterar konstruktionen **kwargs alla nyckelordsbundna argument till en dictionary, som vi sedan kan iterera över i funktionen.

>>> def testa_kwargs(**kwargs):
...     print(type(kwargs))
...     print(kwargs)
... 
>>> testa_kwargs(slump=12, parametrar=21)
<class 'dict'>
{'slump': 12, 'parametrar': 21}

Obs! ”kwargs” är bara en konvention, du kan kalla parametern vad du vill.

Vi kan kontrollera typen av kwargs på samma sätt som vi gjorde med args:

>>> def min_sista_funktion(*args, **kwargs):
...     print('Typ av args: ', type(args))
...     print('args: ', args)
...     print('Typ av kwargs: ', type(kwargs))
...     print('kwargs: ', kwargs)
... 
>>> min_sista_funktion('Python', 'Är', 'Bäst', språk="Python", användare="Många")
Type args:  <class 'tuple'>
args:  ('Python', 'Är', 'Bäst')
Type kwargs:  <class 'dict'>
kwargs:  {'språk': 'Python', 'användare': 'Många'}

Den interna variabeln kwargs blir alltid en dictionary som lagrar alla nyckel-värde-par som skickas till funktionen.

Slutligen, låt oss använda både args och kwargs i samma funktion:

Sammanfattning

  • Uppackningsoperatorerna är oerhört användbara i många sammanhang. Nu vet du hur du använder dem både i enskilda tilldelningar och som funktionsparametrar.
  • I den här handledningen har du lärt dig:
  • * används för att packa upp tupler och listor, och ** används för dictionaries.
  • Du kan använda uppackningsoperatorerna i både funktionsanrop och när du definierar klasser.

args används för att skicka positionsbundna parametrar till funktioner. kwargs används för att skicka nyckelordsbundna parametrar till funktioner.