4. Fler verktyg för kontrollflöde¶
Förutom while
som just introducerades använder Python några fler som vi kommer att stöta på i det här kapitlet.
4.1. if
Statements¶
Den kanske mest välkända typen av uttalande är if
-satsen. Till exempel:
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
...
More
Det kan finnas noll eller fler elif
-delar, och else
-delen är valfri. Nyckelordet ’elif
’ är en förkortning för ’else if’, och är användbart för att undvika överdriven indragning. En if
… elif
… elif
… sekvens är ett substitut för switch
eller case
-satser som finns i andra språk.
Om du jämför samma värde med flera konstanter, eller kontrollerar specifika typer eller attribut, kan du också ha nytta av match
. För mer information se match Uttalanden.
4.2. for
Uttalanden¶
Satsen for
i Python skiljer sig lite från vad du kanske är van vid i C eller Pascal. I stället för att alltid iterera över en aritmetisk progression av tal (som i Pascal), eller ge användaren möjlighet att definiera både iterationssteget och stoppvillkoret (som i C), itererar Pythons for
-sats över objekten i en sekvens (en lista eller en sträng), i den ordning som de förekommer i sekvensen. Till exempel (ingen ordvits avsedd):
>>> # Measure some strings:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12
Kod som ändrar en samling samtidigt som den itererar över samma samling kan vara knepig att få rätt. Istället är det oftast enklare att loopa över en kopia av samlingen eller att skapa en ny samling:
# Skapa en exempelsamling
users = {'Hans': "active", "Éléonore": "inactive", "景太郎": "active"}
# Strategi: Iterera över en kopia
for user, status in users.copy().items():
if status == 'inaktiv':
del användare[användare]
# Strategi: Skapa en ny samling
aktiva_användare = {}
för användare, status i users.items():
om status == "aktiv":
active_users[user] = status
4.3. Funktionen range()
¶
Om du behöver iterera över en sekvens av tal är den inbyggda funktionen range()
praktisk. Den genererar aritmetiska progressioner:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
Den angivna slutpunkten är aldrig en del av den genererade sekvensen; range(10)
genererar 10 värden, de lagliga indexen för objekt i en sekvens med längden 10. Det är möjligt att låta intervallet börja vid ett annat tal, eller att ange ett annat steg (även negativt; ibland kallas detta för ”steg”):
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
För att iterera över indexen i en sekvens kan du kombinera range()
och len()
på följande sätt:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
I de flesta sådana fall är det dock lämpligt att använda funktionen enumerate()
, se Looping-tekniker.
En märklig sak händer om man bara skriver ut ett range:
>>> range(10)
range(0, 10)
På många sätt beter sig objektet som returneras av range()
som om det vore en lista, men det är det faktiskt inte. Det är ett objekt som returnerar de successiva objekten i den önskade sekvensen när du itererar över det, men det gör inte listan, vilket sparar utrymme.
Vi säger att ett sådant objekt är iterable, det vill säga lämpligt som mål för funktioner och konstruktioner som förväntar sig något från vilket de kan få successiva objekt tills tillgången är uttömd. Vi har sett att for
-satsen är en sådan konstruktion, medan ett exempel på en funktion som tar en iterabel är sum()
:
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
Senare kommer vi att se fler funktioner som returnerar iterabler och tar iterabler som argument. I kapitel Datastrukturer, kommer vi att diskutera mer i detalj om list()
.
4.4. break
och continue
Uttalanden¶
Satsen break
bryter ut ur den innersta omslutande for
- eller while
-slingan:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(f"{n} equals {x} * {n//x}")
... break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3
Satsen continue
fortsätter med nästa iteration av slingan:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print(f"Found an even number {num}")
... continue
... print(f"Found an odd number {num}")
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
4.5. else
-klausuler på loopar¶
I en for
- eller while
-slinga kan break
-satsen paras ihop med en else
-sats. Om slingan avslutas utan att break
exekveras, exekveras else
klausulen.
I en for
-slinga utförs else
-satsen efter att slingan har avslutat sin sista iteration, dvs. om ingen paus inträffade.
I en while
-loop körs den efter att loopens villkor blir falskt.
I båda typerna av slingor kommer else
-klausulen inte att exekveras om slingan avslutades med break
. Naturligtvis kommer andra sätt att avsluta slingan tidigt, till exempel ett return
eller ett undantag, också att hoppa över exekveringen av else
-satsen.
Detta exemplifieras i följande for
-slinga, som söker efter primtal:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
(Ja, det här är rätt kod. Titta noga: else
-satsen hör till for
-slingan, inte till if
-satsen.)
Ett sätt att tänka på else-klausulen är att föreställa sig att den paras ihop med if
inuti slingan. När slingan körs kommer den att köra en sekvens som if/if/if/else. if
är inne i slingan och påträffas ett antal gånger. Om villkoret någonsin är sant, kommer ett break
att ske. Om villkoret aldrig är sant kommer else
-klausulen utanför slingan att utföras.
När den används med en slinga har else
-satsen mer gemensamt med else
-satsen i ett try
-uttryck än med den i if
-uttryck: en try
-satsens else
-sats körs när inget undantag inträffar, och en slingas else
-sats körs när inget break
inträffar. Mer information om try
-satsen och undantag finns i Hantering av undantag.
4.6. pass
Uttalanden¶
Satsen pass
gör ingenting. Det kan användas när ett uttalande krävs syntaktiskt men programmet inte kräver någon åtgärd. Till exempel:
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
Detta används ofta för att skapa minimala klasser:
>>> class MyEmptyClass:
... pass
...
Ett annat ställe där pass
kan användas är som en platshållare för en funktion eller en villkorlig kropp när du arbetar med ny kod, så att du kan fortsätta tänka på en mer abstrakt nivå. pass
ignoreras i tysthet:
>>> def initlog(*args):
... pass # Remember to implement this!
...
4.7. match
Uttalanden¶
En match
-sats tar ett uttryck och jämför dess värde med på varandra följande mönster som anges som ett eller flera case-block. Detta är ytligt sett likt en switch-sats i C, Java eller JavaScript (och många andra språk), men det är mer likt mönstermatchning i språk som Rust eller Haskell. Endast det första mönstret som matchar exekveras och det kan också extrahera komponenter (sekvenselement eller objektattribut) från värdet till variabler. Om inget fall matchar exekveras ingen av grenarna.
Den enklaste formen jämför ett ämnesvärde mot en eller flera literaler:
def http_error(status):
matcha status:
fall 400:
returnera "Dålig förfrågan"
fall 404:
returnera "Hittades inte"
fall 418:
returnera "Jag är en tekanna"
fall _:
returnerar "Något är fel med internet"
Observera det sista blocket: ”variabelnamnet” _
fungerar som ett wildcard och misslyckas aldrig med att matcha.
Du kan kombinera flera literaler i ett enda mönster med hjälp av |
(”or”):
fall 401 | 403 | 404:
return "Inte tillåtet"
Mönster kan se ut som uppackningsuppdrag och kan användas för att binda variabler:
# punkten är en (x, y) tupel
matcha punkten:
fall (0, 0):
print("Ursprung")
fall (0, y):
print(f"Y={y}")
fall (x, 0):
print(f"X={x}")
fall (x, y):
print(f"X={x}, Y={y}")
fall _:
raise ValueError("Inte en punkt")
Studera den här noggrant! Det första mönstret har två literaler och kan ses som en utvidgning av det literala mönstret ovan. Men de två följande mönstren kombinerar en bokstav och en variabel, och variabeln binder ett värde från ämnet (point
). Det fjärde mönstret fångar två värden, vilket gör att det konceptuellt liknar uppackningsuppdraget (x, y) = point
.
Om du använder klasser för att strukturera dina data kan du använda klassnamnet följt av en argumentlista som liknar en konstruktor, men med möjlighet att fånga attribut i variabler:
klass Punkt:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(punkt):
matcha punkt:
fall Punkt(x=0, y=0):
print("Ursprung")
fall Punkt(x=0, y=y):
print(f"Y={y}")
fall Punkt(x=x, y=0):
print(f"X={x}")
fall Punkt():
print("Någon annanstans")
fall _:
print("Inte en punkt")
Du kan använda positionella parametrar med vissa inbyggda klasser som ger en ordning för sina attribut (t.ex. dataklasser). Du kan också definiera en specifik position för attribut i mönster genom att ställa in specialattributet __match_args__
i dina klasser. Om det är inställt på (”x”, ”y”) är följande mönster alla likvärdiga (och alla binder attributet y
till variabeln var
):
Punkt(1, var)
Punkt(1, y=var)
Punkt(x=1, y=var)
Punkt(y=var, x=1)
Ett rekommenderat sätt att läsa mönster är att se dem som en utökad form av vad du skulle lägga till vänster om en tilldelning, för att förstå vilka variabler som skulle sättas till vad. Endast de fristående namnen (som var
ovan) tilldelas av en match-sats. Prickade namn (som foo.bar
), attributnamn (x=
och y=
ovan) eller klassnamn (som känns igen på ”(…)” bredvid dem, som Point
ovan) tilldelas aldrig.
Mönster kan vara godtyckligt nästlade. Om vi till exempel har en kort lista med punkter, med __match_args__
tillagt, kan vi matcha den så här:
klass Punkt:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
matchningspunkter:
fall []:
print("Inga punkter")
fall [Punkt(0, 0)]:
print("Ursprunget")
fall [Punkt(x, y)]:
print(f"Enstaka punkt {x}, {y}")
fall [Punkt(0, y1), Punkt(0, y2)]:
print(f"Två på Y-axeln vid {y1}, {y2}")
fall _:
print("Något annat")
Vi kan lägga till en ”om”-klausul i ett mönster, en s.k. ”guard”. Om guarden är falsk går match
vidare till att prova nästa fallblock. Observera att värdeinfångning sker innan guarden utvärderas:
matcha punkt:
case Point(x, y) if x == y:
print(f"Y=X vid {x}")
fall Punkt(x, y):
print(f"Inte på diagonalen")
Flera andra viktiga egenskaper i detta uttalande:
Precis som uppackningsuppdrag har tuple- och listmönster exakt samma betydelse och matchar faktiskt godtyckliga sekvenser. Ett viktigt undantag är att de inte matchar iteratorer eller strängar.
Sekvensmönster stöder utökad uppackning:
[x, y, *rest]
och(x, y, *rest)
fungerar på liknande sätt som uppackningsuppdrag. Namnet efter*
kan också vara_
, så(x, y, *_)
matchar en sekvens med minst två objekt utan att binda de återstående objekten.Mappningsmönster:
{"bandwidth": b, "latency": l}
fångar värdena"bandwidth"
och"latency"
från en ordbok. Till skillnad från sekvensmönster ignoreras extra nycklar. En uppackning som**rest
stöds också. (Men**_
skulle vara överflödigt, så det är inte tillåtet)Undermönster kan fångas upp med hjälp av nyckelordet
as
:case (Punkt(x1, y1), Punkt(x2, y2) som p2): ...
kommer att fånga det andra elementet i inmatningen som
p2
(så länge inmatningen är en sekvens av två punkter)De flesta literaler jämförs med likhet, men singletonerna
True
,False
ochNone
jämförs med identitet.Mönster kan använda namngivna konstanter. Dessa måste vara punkterade namn för att förhindra att de tolkas som fångstvariabel:
from enum import Enum klass Färg(Enum): RED = 'röd' GREEN = 'grön' BLUE = 'blå' color = Color(input("Ange ditt val av 'röd', 'blå' eller 'grön': ")) matcha färg: fall Color.RED: print("Jag ser rött!") fall Färg.GRÖN: print("Gräs är grönt") fall Färg.BLÅ: print("Jag känner mig bluesig :(")
För en mer detaljerad förklaring och ytterligare exempel kan du titta på PEP 636 som är skriven i ett handledningsformat.
4.8. Definiera funktioner¶
Vi kan skapa en funktion som skriver Fibonacci-serien till en godtycklig gräns:
>>> def fib(n): # write Fibonacci series less than n
... """Print a Fibonacci series less than n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Nyckelordet def
introducerar en funktions definition. Det måste följas av funktionsnamnet och den parentesförsedda listan med formella parametrar. De satser som bildar funktionens kropp börjar på nästa rad och måste vara indragna.
Den första satsen i funktionens kropp kan eventuellt vara en stränglitteral; denna stränglitteral är funktionens dokumentationssträng, eller docstring. (Mer om docstrings finns i avsnittet Dokumentationssträngar.) Det finns verktyg som använder docstrings för att automatiskt producera dokumentation på nätet eller i tryck, eller för att låta användaren interaktivt bläddra igenom kod; det är god praxis att inkludera docstrings i kod som du skriver, så gör det till en vana.
När en funktion exekveras introduceras en ny symboltabell som används för funktionens lokala variabler. Mer exakt lagrar alla variabeltilldelningar i en funktion värdet i den lokala symboltabellen, medan variabelreferenser först letar i den lokala symboltabellen, sedan i de lokala symboltabellerna för omslutande funktioner, sedan i den globala symboltabellen och slutligen i tabellen med inbyggda namn. Globala variabler och variabler i inneslutande funktioner kan alltså inte direkt tilldelas ett värde inom en funktion (såvida inte globala variabler namnges i en global
-sats eller variabler i inneslutande funktioner namnges i en nonlocal
-sats), men de kan refereras.
De faktiska parametrarna (argumenten) till ett funktionsanrop introduceras i den lokala symboltabellen för den anropade funktionen när den anropas; argument skickas alltså med call by value (där värdet alltid är en referens till ett objekt, inte objektets värde). [1] När en funktion anropar en annan funktion, eller anropar sig själv rekursivt, skapas en ny lokal symboltabell för det anropet.
En funktionsdefinition associerar funktionsnamnet med funktionsobjektet i den aktuella symboltabellen. Tolken känner igen det objekt som pekas ut av namnet som en användardefinierad funktion. Andra namn kan också peka på samma funktionsobjekt och kan också användas för att komma åt funktionen:
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
Om du kommer från andra språk kanske du invänder att fib
inte är en funktion utan en procedur eftersom den inte returnerar något värde. Faktum är att även funktioner utan return
-sats returnerar ett värde, om än ett ganska tråkigt sådant. Detta värde kallas None
(det är ett inbyggt namn). Att skriva värdet None
undertrycks normalt av tolken om det skulle vara det enda värde som skrivs. Du kan se det om du verkligen vill med hjälp av print()
:
>>> fib(0)
>>> print(fib(0))
None
Det är enkelt att skriva en funktion som returnerar en lista med siffrorna i Fibonacci-serien, istället för att skriva ut den:
>>> def fib2(n): # return Fibonacci series up to n
... """Return a list containing the Fibonacci series up to n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
I det här exemplet demonstreras som vanligt några nya Python-funktioner:
Satsen
return
returnerar med ett värde från en funktion.return
utan ett uttrycksargument returnerarNone
. Att falla av i slutet av en funktion returnerar ocksåNone
.Uttrycket
result.append(a)
anropar en metod i listobjektetresult
. En metod är en funktion som ’tillhör’ ett objekt och namngesobj.methodname
, därobj
är ett objekt (det kan vara ett uttryck) ochmethodname
är namnet på en metod som definieras av objektets typ. Olika typer definierar olika metoder. Metoder av olika typer kan ha samma namn utan att det uppstår tvetydighet. (Det är möjligt att definiera egna objekttyper och metoder med hjälp av klasser, se Klasser). Metodenappend()
som visas i exemplet är definierad för listobjekt; den lägger till ett nytt element i slutet av listan. I detta exempel är det ekvivalent medresult = result + [a]
, men mer effektivt.
4.9. Mer om att definiera funktioner¶
Det är också möjligt att definiera funktioner med ett variabelt antal argument. Det finns tre former som kan kombineras.
4.9.1. Standardargumentvärden¶
Den mest användbara formen är att ange ett standardvärde för ett eller flera argument. Detta skapar en funktion som kan anropas med färre argument än vad den är definierad att tillåta. Till exempel:
def ask_ok(prompt, retries=4, reminder='Försök igen!'):
medan Sann:
svar = input(prompt)
if reply in {'y', 'ye', 'yes'}:
returnera True
om svaret är i {'n', 'no', 'nop', 'nope'}:
returnera False
retries = retries - 1
om retries < 0:
raise ValueError("ogiltigt användarsvar")
print(påminnelse)
Denna funktion kan anropas på flera olika sätt:
endast med det obligatoriska argumentet:
ask_ok('Do you really want to quit?')
som ger ett av de valfria argumenten:
ask_ok('OK to overwrite the file?', 2)
eller till och med ge alla argument:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
I detta exempel introduceras också nyckelordet in
. Detta testar om en sekvens innehåller ett visst värde eller inte.
Standardvärdena utvärderas vid tidpunkten för funktionsdefinitionen i det definierande omfånget, så att
i = 5
def f(arg=i):
print(arg)
i = 6
f()
kommer att skriva ut 5
.
Viktig varning: Standardvärdet utvärderas bara en gång. Detta gör skillnad när standardvärdet är ett föränderligt objekt, t.ex. en lista, en ordbok eller instanser av de flesta klasser. Följande funktion ackumulerar t.ex. de argument som skickas till den vid efterföljande anrop:
def f(a, L=[]):
L.append(a)
returnera L
print(f(1))
print(f(2))
print(f(3))
Detta kommer att skrivas ut
[1]
[1, 2]
[1, 2, 3]
Om du inte vill att standardvärdet ska delas mellan efterföljande anrop kan du istället skriva funktionen så här:
def f(a, L=None):
om L är None:
L = []
L.append(a)
returnerar L
4.9.2. Argument för nyckelord¶
Funktioner kan också anropas med nyckelordsargument av formen kwarg=value
. Till exempel kan följande funktion:
def papegoja(spänning, tillstånd='en stel', åtgärd='voom', typ='norsk blå'):
print("-- Den här papegojan skulle inte", action, end=' ')
print("om du sätter", spänning, "volt genom den.")
print("-- Vacker fjäderdräkt, den", typ)
print("-- Det är", tillstånd, "!")
accepterar ett obligatoriskt argument (voltage
) och tre valfria argument (state
, action
och type
). Denna funktion kan anropas på något av följande sätt:
papegoja(1000) # 1 positionellt argument
parrot(voltage=1000) # 1 nyckelordsargument
parrot(voltage=1000000, action='VOOOOOM') # 2 nyckelordsargument
parrot(action='VOOOOOM', voltage=1000000) # 2 nyckelordsargument
papegoja('en miljon', 'berövad livet', 'hoppa') # 3 positionella argument
papegoja('tusen', tillstånd='skjuter upp prästkragarna') # 1 positionellt, 1 nyckelord
men alla följande anrop skulle vara ogiltiga:
parrot() # nödvändigt argument saknas
parrot(voltage=5.0, 'dead') # icke-nyckelordsargument efter ett nyckelordsargument
parrot(110, voltage=220) # duplicerat värde för samma argument
parrot(skådespelare='John Cleese') # okänt nyckelordsargument
I ett funktionsanrop måste nyckelordsargument följa efter positionsargument. Alla nyckelordsargument som skickas måste matcha ett av de argument som accepteras av funktionen (t.ex. actor
är inte ett giltigt argument för funktionen parrot
), och deras ordning är inte viktig. Detta inkluderar även icke valfria argument (t.ex. parrot(voltage=1000)
är också giltigt). Inget argument får ta emot ett värde mer än en gång. Här är ett exempel som misslyckas på grund av denna begränsning:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'
När en slutlig formell parameter av formen **name
finns, får den en dictionary (se Mappningstyper — dict) som innehåller alla nyckelordsargument utom de som motsvarar en formell parameter. Detta kan kombineras med en formell parameter av formen *name
(beskrivs i nästa underavsnitt) som tar emot en tuple som innehåller de positionella argumenten utöver den formella parameterlistan. (*name
måste förekomma före **name
.) Om vi t.ex. definierar en funktion så här:
def cheeseshop(typ, *argument, **nyckelord):
print("-- Har ni någon", sort, "?")
print("-- Jag är ledsen, men vi har slut på", sort)
för arg i argument:
print(arg)
print("-" * 40)
for kw i nyckelord:
print(kw, ":", nyckelord[kw])
Det skulle kunna kallas så här:
cheeseshop("Limburger","Den är väldigt rinnig, sir.",
"Den är verkligen mycket, MYCKET rinnig, sir.",
butiksinnehavare="Michael Palin",
kund="John Cleese",
sketch="Sketch från ostbutiken")
och naturligtvis skulle det tryckas:
-- Har ni någon Limburger?
-- Jag är ledsen, vi har slut på Limburger
Den är väldigt rinnig, sir.
Den är verkligen mycket, MYCKET rinnande, sir.
----------------------------------------
butiksinnehavare : Michael Palin
kund : John Cleese
skiss : Ostbutikens skiss
Observera att den ordning i vilken nyckelordsargumenten skrivs ut garanterat kommer att överensstämma med den ordning i vilken de angavs i funktionsanropet.
4.9.3. Särskilda parametrar¶
Som standard kan argument skickas till en Python-funktion antingen genom position eller uttryckligen genom nyckelord. För läsbarhet och prestanda är det vettigt att begränsa hur argument kan skickas så att en utvecklare bara behöver titta på funktionsdefinitionen för att avgöra om objekt skickas med position, med position eller nyckelord eller med nyckelord.
En funktionsdefinition kan se ut på följande sätt:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Position eller nyckelord | | - Endast nyckelord
| - Endast nyckelord
-- Endast position
där /
och *
är valfria. Om dessa symboler används anger de vilken typ av parameter som argumenten kan skickas till funktionen med: endast positionell, positionell-eller-nyckelord och endast nyckelord. Nyckelordsparametrar kallas också namngivna parametrar.
4.9.3.1. Positionerings- eller nyckelordsargument¶
Om /
och *
inte finns med i funktionsdefinitionen kan argument skickas till en funktion genom position eller nyckelord.
4.9.3.2. Endast positionella parametrar¶
Om man tittar på detta lite mer i detalj är det möjligt att markera vissa parametrar som positional-only. Om positional-only, spelar parametrarnas ordning roll, och parametrarna kan inte skickas med nyckelord. Positional-only-parametrar placeras före ett /
(framåtriktat snedstreck). /
används för att logiskt separera parametrar som endast är positionella från resten av parametrarna. Om det inte finns någon /
i funktionsdefinitionen, finns det inga enbart positionella parametrar.
Parametrar efter /
kan vara positionella-eller-nyckelord eller endast nyckelord.
4.9.3.3. Argument som endast innehåller nyckelord¶
För att markera parametrar som keyword-only, vilket indikerar att parametrarna måste skickas med nyckelordsargument, placera en *
i argumentlistan precis före den första keyword-only-parametern.
4.9.3.4. Funktion Exempel¶
Tänk på följande exempel på funktionsdefinitioner och var uppmärksam på markörerna /
och *
:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
Den första funktionsdefinitionen, standard_arg
, den mest bekanta formen, lägger inga restriktioner på anropskonventionen och argument kan skickas med position eller nyckelord:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
Den andra funktionen pos_only_arg
är begränsad till att endast använda positionella parametrar eftersom det finns en /
i funktionsdefinitionen:
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
Den tredje funktionen kwd_only_arg
tillåter endast nyckelordsargument som anges med en *
i funktionsdefinitionen:
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
Och den sista använder alla tre anropskonventionerna i samma funktionsdefinition:
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'
Slutligen, betrakta denna funktionsdefinition som har en potentiell kollision mellan det positionella argumentet name
och **kwds
som har name
som nyckel:
def foo(namn, **kwds):
returnerar 'namn' i kwds
Det finns inget möjligt anrop som får den att returnera True
eftersom nyckelordet 'name'
alltid kommer att bindas till den första parametern. Till exempel:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
Men med /
(endast positionella argument) är det möjligt eftersom det tillåter namn
som ett positionellt argument och 'namn'
som en nyckel i nyckelordet arguments:
>>> def foo(name, /, **kwds):
... return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True
Med andra ord kan namnen på parametrar som endast är positionella användas i **kwds
utan tvetydighet.
4.9.3.5. Sammanfattning¶
Användningsfallet avgör vilka parametrar som ska användas i funktionsdefinitionen:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
Som vägledning:
Använd positional-only om du vill att namnet på parametrarna inte ska vara tillgängligt för användaren. Detta är användbart när parameternamn inte har någon verklig betydelse, om du vill tvinga fram ordningen på argumenten när funktionen anropas eller om du behöver ta några positionella parametrar och godtyckliga nyckelord.
Använd endast nyckelord när namnen har betydelse och funktionsdefinitionen blir mer begriplig genom att vara explicit med namn eller om du vill förhindra att användare förlitar sig på positionen för det argument som skickas.
För ett API, använd endast positionell för att förhindra att API-ändringar bryts om parameterns namn ändras i framtiden.
4.9.4. Listor med godtyckliga argument¶
Slutligen, det minst använda alternativet, är att ange att en funktion kan anropas med ett godtyckligt antal argument. Dessa argument kommer att paketeras i en tupel (se Tupler och sekvenser). Före det variabla antalet argument kan noll eller fler normala argument förekomma.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
Normalt kommer dessa variadiska argument sist i listan över formella parametrar, eftersom de samlar upp alla återstående inmatningsargument som skickas till funktionen. Alla formella parametrar som förekommer efter parametern *args
är ”keyword-only”-argument, vilket innebär att de endast kan användas som nyckelord och inte som positionsargument.
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.9.5. Uppackning av argumentlistor¶
Den omvända situationen uppstår när argumenten redan finns i en lista eller tupel men måste packas upp för ett funktionsanrop som kräver separata positionsargument. Till exempel förväntar sig den inbyggda funktionen range()
separata start- och stop-argument. Om de inte finns tillgängliga separat, skriv funktionsanropet med *
-operatorn för att packa upp argumenten ur en lista eller tupel:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
På samma sätt kan ordböcker leverera nyckelordsargument med **
-operatorn:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
4.9.6. Lambda-uttryck¶
Små anonyma funktioner kan skapas med nyckelordet lambda
. Denna funktion returnerar summan av sina två argument: lambda a, b: a+b
. Lambda-funktioner kan användas överallt där funktionsobjekt krävs. De är syntaktiskt begränsade till ett enda uttryck. Semantiskt sett är de bara syntaktiskt socker för en normal funktionsdefinition. Precis som nästlade funktionsdefinitioner kan lambdafunktioner referera till variabler från det scope som innehåller dem:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
I exemplet ovan används ett lambda-uttryck för att returnera en funktion. En annan användning är att skicka en liten funktion som ett argument. Till exempel tar list.sort()
en sorteringsnyckelfunktion key som kan vara en lambda-funktion:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
4.9.7. Dokumentationssträngar¶
Här följer några konventioner om innehåll och formatering av dokumentationssträngar.
Den första raden bör alltid vara en kort och koncis sammanfattning av objektets syfte. För korthetens skull bör inte objektets namn eller typ anges explicit, eftersom dessa finns tillgängliga på annat sätt (utom om namnet råkar vara ett verb som beskriver en funktions funktion). Denna rad ska inledas med en versal och avslutas med en punkt.
Om det finns fler rader i dokumentationssträngen bör den andra raden vara tom, så att sammanfattningen visuellt skiljs från resten av beskrivningen. De följande raderna bör vara ett eller flera stycken som beskriver objektets anropskonventioner, dess bieffekter etc.
Python-parsern tar inte bort indrag från flerradiga stränglitteraler i Python, så verktyg som bearbetar dokumentation måste ta bort indrag om så önskas. Detta görs med hjälp av följande konvention. Den första icke-tomma raden efter den första raden i strängen avgör hur mycket indrag som ska göras i hela dokumentationssträngen. (Vi kan inte använda den första raden eftersom den i allmänhet ligger intill strängens inledande citattecken och dess indragning därför inte syns i strängens bokstavstext) Whitespace som ”motsvarar” detta indrag tas sedan bort från början av alla rader i strängen. Rader som är mindre indragna ska inte förekomma, men om de förekommer ska alla deras ledande blanksteg tas bort. Ekvivalens av blanksteg bör testas efter expansion av tabbar (till 8 blanksteg, normalt).
Här är ett exempel på en dokumentsträng med flera rader:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
4.9.8. Funktionskommentarer¶
Function annotations är helt valfri metadatainformation om de typer som används av användardefinierade funktioner (se PEP 3107 och PEP 484 för mer information).
Annotationer lagras i attributet __annotations__
i funktionen som en ordbok och har ingen effekt på någon annan del av funktionen. Parameterannoteringar definieras av ett kolon efter parameternamnet, följt av ett uttryck som utvärderas till värdet på annoteringen. Returannoteringar definieras av en bokstavlig ->
, följt av ett uttryck, mellan parameterlistan och kolon som anger slutet på def
-satsen. Följande exempel har ett obligatoriskt argument, ett valfritt argument och returvärdet annoterat:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
4.10. Intermezzo: Kodningsstil¶
Nu när du är på väg att skriva längre, mer komplexa stycken Python är det ett bra tillfälle att prata om kodningsstil. De flesta språk kan skrivas (eller mer kortfattat, formateras) i olika stilar; vissa är mer läsbara än andra. Att göra det lätt för andra att läsa din kod är alltid en bra idé, och att använda en trevlig kodningsstil hjälper enormt mycket för det.
För Python har PEP 8 vuxit fram som den stilguide som de flesta projekt följer; den främjar en mycket läsbar och tilltalande kodningsstil. Varje Python-utvecklare bör läsa den vid något tillfälle; här är de viktigaste punkterna extraherade för dig:
Använd 4 spaltmeters indrag och inga tabbar.
4 mellanslag är en bra kompromiss mellan litet indrag (ger större häckningsdjup) och stort indrag (lättare att läsa). Tabbar skapar förvirring och är bäst att utesluta.
Rulla om raderna så att de inte överstiger 79 tecken.
Detta underlättar för användare med små skärmar och gör det möjligt att ha flera kodfiler sida vid sida på större skärmar.
Använd tomma rader för att separera funktioner och klasser, och större kodblock inuti funktioner.
När det är möjligt, placera kommentarer på en egen rad.
Använd docstrings.
Använd mellanslag runt operatorer och efter kommatecken, men inte direkt inuti parenteser:
a = f(1, 2) + g(3, 4)
.Namnge dina klasser och funktioner på ett konsekvent sätt; konventionen är att använda
UpperCamelCase
för klasser ochlowercase_with_underscores
för funktioner och metoder. Använd alltidself
som namn på det första metodargumentet (se En första titt på klasserna för mer information om klasser och metoder).Använd inte avancerade kodningar om din kod är avsedd att användas i internationella miljöer. Pythons standard, UTF-8, eller till och med vanlig ASCII fungerar bäst i alla fall.
Använd inte heller icke-ASCII-tecken i identifierare om det bara finns en liten risk för att personer som talar ett annat språk kommer att läsa eller underhålla koden.
Fotnoter