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-satser¶
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-satser.
4.2. for-satser¶
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 vanligtvis enklare att loopa över en kopia av samlingen eller att skapa en ny samling:
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
4.3. Funktionen :func:`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
Later we will see more functions that return iterables and take iterables as
arguments. In chapter Datastrukturer, we will discuss in more detail about
list().
4.4. break and continue Statements, and else Clauses on Loops¶
The break statement breaks out of the innermost enclosing
for or while loop.
A for or while loop can include an else clause.
In a for loop, the else clause is executed
after the loop reaches its final iteration.
I en while-loop körs den efter att loopens villkor blir falskt.
In either kind of loop, the else clause is not executed
if the loop was terminated by a break.
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
(Yes, this is the correct code. Look closely: the else clause belongs to
the for loop, not the if statement.)
When used with a loop, the else clause has more in common with the
else clause of a try statement than it does with that of
if statements: a try statement’s else clause runs
when no exception occurs, and a loop’s else clause runs when no break
occurs. For more on the try statement and exceptions, see
Handling Exceptions.
The continue statement, also borrowed from C, continues with the next
iteration of the loop:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("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. pass-satser¶
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å. Nyckelordet:!pass ignoreras i tysthet:
>>> def initlog(*args):
... pass # Remember to implement this!
...
4.6. match-satser¶
A match statement takes an expression and compares its value to successive
patterns given as one or more case blocks. This is superficially
similar to a switch statement in C, Java or JavaScript (and many
other languages), but it’s more similar to pattern matching in
languages like Rust or Haskell. Only the first pattern that matches
gets executed and it can also extract components (sequence elements
or object attributes) from the value into variables.
Den enklaste formen jämför ett ämnesvärde mot en eller flera literaler:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
Note the last block: the ”variable name” _ acts as a wildcard and
never fails to match. If no case matches, none of the branches is executed.
Du kan kombinera flera literaler i ett enda mönster med hjälp av | (”eller”):
case 401 | 403 | 404:
return "Not allowed"
Mönster kan se ut som uppackningsuppdrag och kan användas för att binda variabler:
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
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:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
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):
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(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:
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
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:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
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**reststö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 (Point(x1, y1), Point(x2, y2) as 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,FalseochNonejämförs med identitet.Patterns may use named constants. These must be dotted names to prevent them from being interpreted as capture variable:
from enum import Enum class Color(Enum): RED = 'red' GREEN = 'green' BLUE = 'blue' color = Color(input("Enter your choice of 'red', 'blue' or 'green': ")) match color: case Color.RED: print("I see red!") case Color.GREEN: print("Grass is green") case Color.BLUE: print("I'm feeling the blues :(")
För en mer detaljerad förklaring och ytterligare exempel kan du titta på PEP 636 som är skriven i ett handledningsformat.
4.7. Definiera funktioner¶
Vi kan skapa en funktion som skriver Fibonacci-serien till en godtycklig gräns:
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to 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 Dokumentation Strä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. Närmare bestämt 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). [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
returnreturnerar med ett värde från en funktion.returnutan ett uttrycksargument returnerarNone. Att falla av i slutet av en funktion returnerar ocksåNone.The statement
result.append(a)calls a method of the list objectresult. A method is a function that ’belongs’ to an object and is namedobj.methodname, whereobjis some object (this may be an expression), andmethodnameis the name of a method that is defined by the object’s type. Different types define different methods. Methods of different types may have the same name without causing ambiguity. (It is possible to define your own object types and methods, using classes, see Klasser) The methodappend()shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent toresult = result + [a], but more efficient.
4.8. 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.8.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='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
Denna funktion kan anropas på flera olika sätt:
ger bara det obligatoriska argumentet:
ask_ok('Vill du verkligen sluta?')som ger ett av de valfria argumenten:
ask_ok('OK att skriva över filen?', 2)`eller till och med ge alla argument:
ask_ok('OK att skriva över filen?', 2, 'Kom igen, bara ja eller nej!')
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)
return 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):
if L is None:
L = []
L.append(a)
return L
4.8.2. Argument för nyckelord¶
Funktioner kan också anropas med nyckelordsargument av formen kwarg=värde. Till exempel kan följande funktion:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
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:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
men alla följande anrop skulle vara ogiltiga:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
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 Mapping Types — 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(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
Det skulle kunna kallas så här:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
och naturligtvis skulle det tryckas:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
Observera att den ordning i vilken nyckelordsargumenten skrivs ut garanterat kommer att överensstämma med den ordning i vilken de angavs i funktionsanropet.
4.8.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):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
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.8.3.1. Positionerings- eller nyckelordsargument¶
Om / och * inte finns med i funktionsdefinitionen kan argument skickas till en funktion genom position eller nyckelord.
4.8.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.8.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.8.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'
The third function kwd_only_args only allows keyword arguments as indicated
by a * in the function definition:
>>> 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(name, **kwds):
return 'name' in 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.8.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.8.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.8.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.8.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
The above example uses a lambda expression to return a function. Another use is to pass a small function as an argument:
>>> 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.8.7. Dokumentation Strä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.
The Python parser does not strip indentation from multi-line string literals in Python, so tools that process documentation have to strip indentation if desired. This is done using the following convention. The first non-blank line after the first line of the string determines the amount of indentation for the entire documentation string. (We can’t use the first line since it is generally adjacent to the string’s opening quotes so its indentation is not apparent in the string literal.) Whitespace ”equivalent” to this indentation is then stripped from the start of all lines of the string. Lines that are indented less should not occur, but if they occur all their leading whitespace should be stripped. Equivalence of whitespace should be tested after expansion of tabs (to 8 spaces, normally).
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.8.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).
Annotations are stored in the __annotations__
attribute of the function as a dictionary and have no effect on any other part of the
function. Parameter annotations are defined by a colon after the parameter name, followed
by an expression evaluating to the value of the annotation. Return annotations are
defined by a literal ->, followed by an expression, between the parameter
list and the colon denoting the end of the def statement. The
following example has a required argument, an optional argument, and the return
value annotated:
>>> 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.9. Intermezzo: Kodningsstil¶
Now that you are about to write longer, more complex pieces of Python, it is a good time to talk about coding style. Most languages can be written (or more concise, formatted) in different styles; some are more readable than others. Making it easy for others to read your code is always a good idea, and adopting a nice coding style helps tremendously for that.
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.
Radbryt 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
UpperCamelCaseför klasser ochlowercase_with_underscoresför funktioner och metoder. Använd alltidselfsom 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