Vanliga frågor om programmering¶
Allmänna frågor¶
Finns det en debugger på källkodsnivå med brytpunkter, single-stepping, etc¶
Ja.
Flera debuggers för Python beskrivs nedan, och den inbyggda funktionen breakpoint()
gör att du kan hoppa in i vilken som helst av dem.
Modulen pdb är en enkel men adekvat felsökare i konsolläge för Python. Den är en del av Pythons standardbibliotek och dokumenteras i Library Reference Manual
. Du kan också skriva din egen felsökare genom att använda koden för pdb som ett exempel.
Den interaktiva utvecklingsmiljön IDLE, som ingår i standarddistributionen av Python (normalt tillgänglig som Tools/scripts/idle3), innehåller en grafisk felsökare.
PythonWin är en Python IDE som innehåller en GUI-felsökare baserad på pdb. PythonWin-felsökaren färgar brytpunkter och har en hel del coola funktioner, till exempel felsökning av icke-PythonWin-program. PythonWin är tillgängligt som en del av pywin32-projektet och som en del av ActivePython-distributionen.
Eric är en IDE som bygger på PyQt och redigeringskomponenten Scintilla.
trepan3k är en gdb-liknande felsökare.
Visual Studio Code är en IDE med felsökningsverktyg som kan integreras med programvara för versionshantering.
Det finns ett antal kommersiella Python IDE:er som innehåller grafiska debuggers. De inkluderar:
Finns det verktyg som hjälper till att hitta buggar eller utföra statisk analys?¶
Ja.
Pylint och Pyflakes gör grundläggande kontroller som hjälper dig att hitta buggar snabbare.
Statiska typkontrollprogram som Mypy, Pyre och Pytype kan kontrollera typtips i Pythons källkod.
Hur kan jag skapa en fristående binär från ett Python-skript?¶
Du behöver inte kunna kompilera Python till C-kod om du bara vill ha ett fristående program som användarna kan ladda ner och köra utan att först behöva installera Python-distributionen. Det finns ett antal verktyg som fastställer den uppsättning moduler som krävs av ett program och binder samman dessa moduler med en Python-binärfil för att producera en enda körbar fil.
Ett sätt är att använda verktyget freeze, som ingår i Pythons källträd som Tools/freeze. Det konverterar Pythons bytekod till C-arrayer; med en C-kompilator kan du bädda in alla dina moduler i ett nytt program, som sedan länkas med Pythons standardmoduler.
Det fungerar genom att skanna din källkod rekursivt efter importmeddelanden (i båda formerna) och leta efter modulerna i Pythons standardsökväg samt i källkatalogen (för inbyggda moduler). Därefter omvandlas bytekoden för moduler skrivna i Python till C-kod (array-initialiserare som kan omvandlas till kodobjekt med hjälp av marshal-modulen) och en skräddarsydd konfigurationsfil skapas som endast innehåller de inbyggda moduler som faktiskt används i programmet. Därefter kompileras den genererade C-koden och länkas med resten av Python-tolken för att bilda en fristående binär fil som fungerar precis som ditt skript.
Följande paket kan hjälpa till att skapa körbara konsol- och GUI-program:
Nuitka (plattformsoberoende)
PyInstaller (plattformsoberoende)
PyOxidizer (plattformsoberoende)
cx_Freeze (plattformsoberoende)
py2app (endast macOS)
py2exe (endast Windows)
Finns det kodningsstandarder eller en stilguide för Python-program?¶
Ja. Den kodningsstil som krävs för standardbiblioteksmoduler är dokumenterad som PEP 8.
Kärnspråk¶
Varför får jag ett UnboundLocalError när variabeln har ett värde?¶
Det kan vara en överraskning att få UnboundLocalError
i tidigare fungerande kod när den ändras genom att lägga till en assignment-sats någonstans i en funktions kropp.
Den här koden:
>>> x = 10
>>> def bar():
... print(x)
...
>>> bar()
10
fungerar, men den här koden:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
resulterar i en UnboundLocalError
:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
Det beror på att när du gör en tilldelning till en variabel i ett scope, blir variabeln lokal i detta scope och skuggar alla variabler med liknande namn i det yttre scopet. Eftersom den sista satsen i foo tilldelar ett nytt värde till x
, känner kompilatorn igen den som en lokal variabel. Följaktligen när den tidigare print(x)
försöker skriva ut den oinitialiserade lokala variabeln och ett fel uppstår.
I exemplet ovan kan du komma åt den yttre scopevariabeln genom att deklarera den som global:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
...
>>> foobar()
10
Denna uttryckliga deklaration krävs för att påminna dig om att du faktiskt ändrar variabelns värde i det yttre omfånget (till skillnad från den ytligt sett analoga situationen med klass- och instansvariabler):
>>> print(x)
11
Du kan göra något liknande i ett nästlat scope med hjälp av nyckelordet nonlocal
:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
...
>>> foo()
10
11
Vilka är reglerna för lokala och globala variabler i Python?¶
I Python är variabler som endast refereras till inom en funktion implicit globala. Om en variabel tilldelas ett värde någonstans inom funktionens kropp antas den vara lokal om den inte uttryckligen deklareras som global.
Även om det är lite förvånande i början, förklarar en stunds övervägande detta. Å ena sidan ger kravet på global
för tilldelade variabler en spärr mot oavsiktliga bieffekter. Å andra sidan, om global
krävdes för alla globala referenser, skulle du använda global
hela tiden. Man skulle vara tvungen att deklarera varje referens till en inbyggd funktion eller till en komponent i en importerad modul som global. Denna röra skulle omintetgöra nyttan av global
-deklarationen för att identifiera bieffekter.
Varför returnerar lambdas som definieras i en loop med olika värden alla samma resultat?¶
Antag att du använder en for-slinga för att definiera några olika lambdas (eller till och med vanliga funktioner), t.ex.:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
Detta ger dig en lista som innehåller 5 lambdas som beräknar x**2
. Du kan förvänta dig att när de anropas skulle de returnera 0
, 1
, 4
, 9
och 16
. Men när du faktiskt försöker kommer du att se att de alla returnerar 16
:
>>> squares[2]()
16
>>> squares[4]()
16
Detta beror på att x
inte är lokalt för lambdan utan definieras i det yttre scope, och den nås när lambdan anropas — inte när den definieras. I slutet av slingan är värdet på x
4
, så alla funktioner returnerar nu 4**2
, dvs 16
. Du kan också verifiera detta genom att ändra värdet på x
och se hur resultaten av lambdas ändras:
>>> x = 8
>>> squares[2]()
64
För att undvika detta måste du spara värdena i variabler som är lokala för lambdan, så att de inte är beroende av värdet på den globala x
:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Här skapar n=x
en ny variabel n
som är lokal för lambdan och som beräknas när lambdan definieras så att den har samma värde som x
hade vid den tidpunkten i loopen. Detta innebär att värdet på n
kommer att vara 0
i den första lambdan, 1
i den andra, 2
i den tredje och så vidare. Därför kommer varje lambda nu att returnera rätt resultat:
>>> squares[2]()
4
>>> squares[4]()
16
Observera att detta beteende inte är unikt för lambdas, utan även gäller för vanliga funktioner.
Vilka är de ”bästa metoderna” för att använda import i en modul?¶
I allmänhet ska du inte använda from modulename import *
. Om du gör det blir importörens namnrymd rörig och det blir mycket svårare för linters att upptäcka odefinierade namn.
Importera moduler längst upp i en fil. På så sätt blir det tydligt vilka andra moduler din kod kräver och du undviker frågor om modulnamnet är inom räckvidden. Om du använder en import per rad är det enkelt att lägga till och ta bort modulimporter, men om du använder flera importer per rad tar det mindre plats på skärmen.
Det är bra om du importerar moduler i följande ordning:
biblioteksmoduler från tredje part (allt som installeras i Pythons site-packages-katalog) – t.ex.
dateutil
,requests
,PIL.Image
lokalt utvecklade moduler
Det är ibland nödvändigt att flytta import till en funktion eller klass för att undvika problem med cirkulär import. Gordon McMillan säger:
Cirkulär import fungerar bra när båda modulerna använder importformen ”import <module>”. De misslyckas när den andra modulen vill hämta ett namn från den första (”from module import name”) och importen sker på högsta nivån. Det beror på att namnen i den första modulen ännu inte är tillgängliga, eftersom den första modulen är upptagen med att importera den andra.
I det här fallet, om den andra modulen bara används i en funktion, kan importen enkelt flyttas till den funktionen. När importen anropas kommer den första modulen att ha initialiserats färdigt och den andra modulen kan göra sin import.
Det kan också vara nödvändigt att flytta importen från den översta kodnivån om några av modulerna är plattformsspecifika. I så fall kanske det inte ens är möjligt att importera alla moduler högst upp i filen. I så fall är det ett bra alternativ att importera rätt moduler i motsvarande plattformsspecifika kod.
Flytta bara import till ett lokalt scope, t.ex. inuti en funktionsdefinition, om det är nödvändigt för att lösa ett problem, t.ex. för att undvika cirkulär import eller för att minska initialiseringstiden för en modul. Den här tekniken är särskilt användbar om många av importerna är onödiga beroende på hur programmet exekveras. Du kanske också vill flytta importen till en funktion om modulerna bara används i den funktionen. Observera att det kan vara dyrt att ladda en modul första gången eftersom modulen initialiseras en gång, men att ladda en modul flera gånger är praktiskt taget gratis och kostar bara ett par ordboksuppslagningar. Även om modulnamnet har försvunnit finns modulen förmodligen tillgänglig i sys.modules
.
Hur kan jag skicka valfria parametrar eller nyckelordsparametrar från en funktion till en annan?¶
Samla argumenten med hjälp av specifikatorerna *
och **
i funktionens parameterlista, så att du får de positionella argumenten som en tupel och nyckelordsargumenten som en ordbok. Du kan sedan skicka dessa argument när du anropar en annan funktion genom att använda *
och **
:
def f(x, *args, **kwargs):
...
kwargs['bredd'] = '14.3c'
...
g(x, *args, **kwargs)
Vad är skillnaden mellan argument och parametrar?¶
Parametrar definieras av de namn som förekommer i en funktionsdefinition, medan argument är de värden som faktiskt skickas till en funktion när den anropas. Parametrar definierar vilken typ av argument som en funktion kan acceptera. Till exempel, givet funktionsdefinitionen:
def func(foo, bar=None, **kwargs):
pass
foo, bar och kwargs är parametrar för func
. Men när man anropar func
, till exempel:
func(42, bar=314, extra=somevar)
värdena 42
, 314
och somevar
är argument.
Varför ändrades listan ”y” samtidigt som listan ”x” ändrades?¶
Om du skrev kod som:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
kanske du undrar varför ett tillägg av ett element till y
ändrade x
också.
Det finns två faktorer som ger detta resultat:
Variabler är helt enkelt namn som refererar till objekt. Om du gör
y = x
skapas inte en kopia av listan - det skapas en ny variabely
som refererar till samma objekt somx
refererar till. Det betyder att det bara finns ett objekt (listan) och att bådex
ochy
refererar till det.Listor är mutable, vilket innebär att du kan ändra deras innehåll.
Efter anropet till append()
har innehållet i det föränderliga objektet ändrats från []
till [10]
. Eftersom båda variablerna refererar till samma objekt får man tillgång till det modifierade värdet [10]
om man använder något av namnen.
Om vi istället tilldelar ett oföränderligt objekt till x
:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
kan vi se att i detta fall är x
och y
inte lika längre. Detta beror på att heltal är immutable, och när vi gör x = x + 1
muterar vi inte int 5
genom att öka dess värde; istället skapar vi ett nytt objekt (int 6
) och tilldelar det till x
(det vill säga ändrar vilket objekt x
refererar till). Efter denna tilldelning har vi två objekt (int 6
och 5
) och två variabler som hänvisar till dem (x
hänvisar nu till 6
men y
hänvisar fortfarande till 5
).
Vissa operationer (till exempel y.append(10)
och y.sort()
) muterar objektet, medan ytligt sett liknande operationer (till exempel y = y + [10]
och sorted(y)
) skapar ett nytt objekt. I allmänhet i Python (och i alla fall i standardbiblioteket) kommer en metod som muterar ett objekt att returnera None
för att undvika att de två typerna av operationer förväxlas. Så om du felaktigt skriver y.sort()
och tror att det kommer att ge dig en sorterad kopia av y
, kommer du istället att sluta med None
, vilket sannolikt kommer att leda till att ditt program genererar ett lättdiagnostiserat fel.
Det finns dock en klass av operationer där samma operation ibland har olika beteenden med olika typer: de utökade tilldelningsoperatorerna. Till exempel muterar +=
listor men inte tupler eller ints (a_list += [1, 2, 3]
motsvarar a_list.extend([1, 2, 3])
och muterar a_list
, medan some_tuple += (1, 2, 3)
och some_int += 1
skapar nya objekt).
Med andra ord..:
Om vi har ett muterbart objekt (
list
,dict
,set
, etc.), kan vi använda vissa specifika operationer för att mutera det och alla variabler som hänvisar till det kommer att se förändringen.Om vi har ett oföränderligt objekt (
str
,int
,tuple
, etc.) kommer alla variabler som refererar till det alltid att ha samma värde, men operationer som omvandlar det värdet till ett nytt värde returnerar alltid ett nytt objekt.
Om du vill veta om två variabler refererar till samma objekt eller inte, kan du använda operatorn is
eller den inbyggda funktionen id()
.
Hur skriver jag en funktion med utparametrar (call by reference)?¶
Kom ihåg att argument skickas via assignment i Python. Eftersom tilldelning bara skapar referenser till objekt finns det inget alias mellan ett argumentnamn i den som anropar och den som tar emot anropet, och därmed inget anrop genom referens i sig. Du kan uppnå önskad effekt på ett antal olika sätt.
Genom att returnera en tupel av resultaten:
>>> def func1(a, b): ... a = 'new-value' # a and b are local names ... b = b + 1 # assigned to new objects ... return a, b # return new values ... >>> x, y = 'old-value', 99 >>> func1(x, y) ('new-value', 100)
Detta är nästan alltid den tydligaste lösningen.
Genom att använda globala variabler. Detta är inte trådsäkert och rekommenderas inte.
Genom att skicka ett muterbart (förändringsbart på plats) objekt:
>>> def func2(a): ... a[0] = 'new-value' # 'a' references a mutable list ... a[1] = a[1] + 1 # changes a shared object ... >>> args = ['old-value', 99] >>> func2(args) >>> args ['new-value', 100]
Genom att skicka in en ordbok som muteras:
>>> def func3(args): ... args['a'] = 'new-value' # args is a mutable dictionary ... args['b'] = args['b'] + 1 # change it in-place ... >>> args = {'a': 'old-value', 'b': 99} >>> func3(args) >>> args {'a': 'new-value', 'b': 100}
Eller samla ihop värden i en klassinstans:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(args): ... args.a = 'new-value' # args is a mutable Namespace ... args.b = args.b + 1 # change object in-place ... >>> args = Namespace(a='old-value', b=99) >>> func4(args) >>> vars(args) {'a': 'new-value', 'b': 100}
Det finns nästan aldrig någon bra anledning att göra det så här komplicerat.
Det bästa alternativet är att returnera en tupel som innehåller flera resultat.
Hur gör man en funktion av högre ordning i Python?¶
Du har två val: du kan använda nästlade scopes eller så kan du använda anropsbara objekt. Anta till exempel att du vill definiera linear(a,b)
som returnerar en funktion f(x)
som beräknar värdet a*x+b
. Använda nästlade scopes:
def linear(a, b):
def resultat(x):
returnerar a * x + b
returnera resultat
Eller använda ett anropsbart objekt:
klass linjär:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
I båda fallen gäller:
skatter = linjär(0,3, 2)
ger ett anropbart objekt där taxes(10e6) == 0.3 * 10e6 + 2
.
Metoden med anropsbara objekt har nackdelen att den är lite långsammare och resulterar i något längre kod. Observera dock att en samling callables kan dela sin signatur via arv:
klass exponentiell(linjär):
# __init__ nedärvd
def __call__(self, x):
return self.a * (x ** self.b)
Objekt kan kapsla in tillstånd för flera metoder:
klassräknare:
värde = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = räknare()
inc, dec, reset = count.up, count.down, count.set
Här fungerar inc()
, dec()
och reset()
som funktioner som delar samma räknevariabel.
Hur kopierar jag ett objekt i Python?¶
I allmänhet kan du prova copy.copy()
eller copy.deepcopy()
för det allmänna fallet. Alla objekt kan inte kopieras, men de flesta kan det.
Vissa objekt kan kopieras lättare. Dictionaries har en copy()
metod:
newdict = olddict.copy()
Sekvenser kan kopieras genom att skära:
ny_l = l[:]
Hur hittar jag metoder eller attribut för ett objekt?¶
För en instans x
av en användardefinierad klass returnerar dir(x)
en alfabetiserad lista över de namn som innehåller de instansattribut, metoder och attribut som definieras av klassen.
Hur kan min kod ta reda på namnet på ett objekt?¶
Generellt sett kan det inte det, eftersom objekt egentligen inte har några namn. I princip binder assignment alltid ett namn till ett värde; samma sak gäller för def
och class
statements, men i det fallet är värdet en callable. Tänk på följande kod:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
Man kan hävda att klassen har ett namn: även om den är bunden till två namn och anropas via namnet B
rapporteras den skapade instansen fortfarande som en instans av klassen A
. Det är dock omöjligt att säga om instansens namn är a
eller b
, eftersom båda namnen är bundna till samma värde.
Generellt sett bör det inte vara nödvändigt för din kod att ”känna till namnen” på vissa värden. Om du inte avsiktligt skriver introspektiva program är detta vanligtvis en indikation på att det kan vara bra att ändra tillvägagångssätt.
I comp.lang.python gav Fredrik Lundh en gång en utmärkt liknelse som svar på denna fråga:
På samma sätt som du får reda på namnet på katten du hittade på din veranda: katten (objektet) själv kan inte berätta vad den heter, och den bryr sig egentligen inte - så det enda sättet att få reda på vad den heter är att fråga alla dina grannar (namnrymder) om det är deras katt (objekt)…
…. och bli inte förvånad om du upptäcker att den är känd under många namn, eller inget namn alls!
Vad är det med kommatecknets företräde?¶
Komma är inte en operator i Python. Tänk på denna session:
>>> "a" in "b", "a"
(False, 'a')
Eftersom kommatecknet inte är en operator, utan en separator mellan uttryck, utvärderas ovanstående som om du hade skrivit:
("a" i "b"), "a"
inte:
"a" i ("b", "a")
Detsamma gäller för de olika tilldelningsoperatorerna (=
, +=
etc). De är egentligen inte operatorer utan syntaktiska avgränsare i assignment-satser.
Finns det en motsvarighet till C:s ”?:” ternära operator?¶
Ja, det finns det. Syntaxen är som följer:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x om x < y annars y
Innan denna syntax introducerades i Python 2.5 var ett vanligt idiom att använda logiska operatorer:
[uttryck] och [on_true] eller [on_false]
Detta idiom är dock osäkert, eftersom det kan ge fel resultat när on_true har ett falskt booleskt värde. Därför är det alltid bättre att använda formen ... if ... else ...
.
Är det möjligt att skriva obfuskerade one-liners i Python?¶
Ja, det gör jag. Vanligtvis görs detta genom att nesta lambda
inom lambda
. Se följande tre exempel, något anpassade från Ulf Bartelt:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# \___ ___/ \___ ___/ | | |__ lines on screen
# V V | |______ columns on screen
# | | |__________ maximum of "iterations"
# | |_________________ range on y axis
# |____________________________ range on x axis
Prova inte det här hemma, barn!
Vad betyder snedstrecket(/) i parameterlistan för en funktion?¶
Ett snedstreck i argumentlistan för en funktion anger att parametrarna före det är enbart positionella. Endast positionella parametrar är de som saknar ett externt användbart namn. När du anropar en funktion som accepterar enbart positionella parametrar mappas argumenten till parametrar enbart baserat på deras position. Exempelvis är divmod()
en funktion som accepterar enbart positionella parametrar. Dess dokumentation ser ut så här:
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
Snedstrecket i slutet av parameterlistan betyder att båda parametrarna endast är positionella. Således skulle ett anrop av divmod()
med nyckelordsargument leda till ett fel:
>>> divmod(x=3, y=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments
Siffror och strängar¶
Hur anger jag hexadecimala och oktala heltal?¶
För att ange en oktalsiffra föregår du oktalvärdet med en nolla och sedan ett litet eller stort ”o”. Om du till exempel vill ställa in variabeln ”a” till oktalvärdet ”10” (8 i decimal) skriver du:
>>> a = 0o10
>>> a
8
Hexadecimalt är lika enkelt. Det hexadecimala talet föregås helt enkelt av en nolla och sedan ett ”x” med liten eller stor bokstav. Hexadecimala siffror kan anges med små eller stora bokstäver. Till exempel i Python-tolken:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
Varför returnerar -22 // 10 -3?¶
Den drivs främst av önskemålet att i % j
ska ha samma tecken som j
. Om du vill ha det, och också vill ha:
i == (i // j) * j + (i % j)
så måste heltalsdivision ge golvet. C kräver också att identiteten ska gälla, och då måste kompilatorer som trunkerar i // j
se till att i % j
har samma tecken som i
.
Det finns få verkliga användningsfall för i % j
när j
är negativ. När j
är positiv finns det många, och i praktiskt taget alla är det mer användbart för i % j
att vara >= 0
. Om klockan visar 10 nu, vad visade den då för 200 timmar sedan? -190 % 12 == 2
är användbart; -190 % 12 == -10
är en bugg som väntar på att bita.
Hur får jag int literal-attribut istället för SyntaxError?¶
Att försöka slå upp ett int
bokstavligt attribut på normalt sätt ger ett SyntaxError
eftersom punkten ses som ett decimaltecken:
>>> 1.__class__
File "<stdin>", line 1
1.__class__
^
SyntaxError: invalid decimal literal
Lösningen är att skilja bokstav från punkt med antingen ett mellanslag eller en parentes.
>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>
Hur konverterar jag en sträng till ett tal?¶
För heltal används den inbyggda typkonstruktören int()
, t.ex. int('144') == 144
. På samma sätt konverterar float()
till ett flyttal, t.ex. float('144') == 144.0
.
Som standard tolkar dessa talet som decimaltal, så att int('0144') == 144
är sant och int('0x144')
ger upphov till ValueError
. int(string, base)
tar basen att konvertera från som ett andra valfritt argument, så int( '0x144', 16) == 324
. Om basen anges som 0 tolkas talet enligt Pythons regler: ett inledande ’0o’ indikerar oktal och ’0x’ indikerar ett hextal.
Använd inte den inbyggda funktionen eval()
om allt du behöver är att konvertera strängar till tal. eval()
blir betydligt långsammare och det innebär en säkerhetsrisk: någon kan skicka ett Python-uttryck till dig som kan ha oönskade bieffekter. Till exempel skulle någon kunna skicka __import__('os').system("rm -rf $HOME")
vilket skulle radera din hemkatalog.
eval()
har också effekten att siffror tolkas som Python-uttryck, så att t.ex. eval('09')
ger ett syntaxfel eftersom Python inte tillåter inledande ’0’ i ett decimaltal (utom ’0’).
Hur konverterar jag ett tal till en sträng?¶
För att konvertera t.ex. talet 144
till strängen '144'
, använd den inbyggda typkonstruktören str()
. Om du vill ha en hexadecimal eller oktal representation, använd de inbyggda funktionerna hex()
eller oct()
. För tjusig formatering, se avsnitten f-strängar och Format String Syntax, t.ex. "{:04d}".format(144)
ger '0144'
och "{:.3f}".format(1.0/3.0)
ger '0.333'
.
Hur ändrar jag en sträng på plats?¶
Det kan du inte, eftersom strängar är oföränderliga. I de flesta situationer bör du helt enkelt konstruera en ny sträng från de olika delar som du vill sätta ihop den av. Men om du behöver ett objekt med möjlighet att modifiera unicode-data på plats kan du försöka använda ett io.StringIO
-objekt eller array
-modulen:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', 'yello, world')
>>> a.tounicode()
'yello, world'
Hur använder jag strängar för att anropa funktioner/metoder?¶
Det finns olika tekniker.
Det bästa är att använda en ordbok som mappar strängar till funktioner. Den främsta fördelen med den här tekniken är att strängarna inte behöver matcha namnen på funktionerna. Detta är också den primära teknik som används för att emulera en fallkonstruktion:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Observera avsaknad av parenteser för funcs dispatch[get_input()]() # Notera efterföljande parenteser för att anropa funktion
Använd den inbyggda funktionen
getattr()
:import foo getattr(foo, 'bar')()
Observera att
getattr()
fungerar på alla objekt, inklusive klasser, klassinstanser, moduler och så vidare.Detta används på flera ställen i standardbiblioteket, till exempel så här:
klass Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f()
Använd
locals()
för att lösa upp funktionsnamnet:def myFunc(): print("hallå") fname = "myFunc" f = locals()[fnamn] f()
Finns det en motsvarighet till Perls chomp()
för att ta bort efterföljande nya rader från strängar?¶
Du kan använda S.rstrip("\r\n")
för att ta bort alla förekomster av en radavslutare från slutet av strängen S
utan att ta bort andra efterföljande blanksteg. Om strängen S
representerar mer än en rad, med flera tomma rader i slutet, kommer radavslutarna för alla de tomma raderna att tas bort:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
Eftersom detta vanligtvis bara är önskvärt när man läser text en rad i taget, fungerar det bra att använda S.rstrip()
på detta sätt.
Finns det en motsvarighet till scanf()
eller sscanf()
?¶
Inte som sådan.
För enkel parsning av indata är det enklast att dela upp raden i ord som är avgränsade med blanksteg med hjälp av metoden split()
för strängobjekt och sedan konvertera decimalsträngar till numeriska värden med hjälp av int()
eller float()
. split()
stöder en valfri parameter ”sep” som är användbar om raden använder något annat än blanksteg som avgränsare.
För mer komplicerad parsning av indata är reguljära uttryck kraftfullare än C:s sscanf
och bättre lämpade för uppgiften.
Vad betyder UnicodeDecodeError
eller UnicodeEncodeError
fel?¶
Se Unicode HOWTO.
Kan jag avsluta en rå sträng med ett udda antal backslash?¶
En rå sträng som slutar med ett udda antal backslashes kommer att undkomma strängens quote:
>>> r'C:\this\will\not\work\'
File "<stdin>", line 1
r'C:\this\will\not\work\'
^
SyntaxError: unterminated string literal (detected at line 1)
Det finns flera lösningar på detta. En är att använda vanliga strängar och dubbla de bakre bindestrecken:
>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'
Ett annat sätt är att konkatenera en reguljär sträng som innehåller en undangömd backslash till råsträngen:
>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'
Det är också möjligt att använda os.path.join()
för att lägga till ett backslash i Windows:
>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'
Observera att även om en backslash ”escapar” ett citattecken för att avgöra var råsträngen slutar, sker ingen escaping när råsträngens värde tolkas. Det vill säga, det bakre snedstrecket finns kvar i värdet av den råa strängen:
>>> r'backslash\'preserved'
"backslash\\'preserved"
Se även specifikationen i language reference.
Prestanda¶
Mitt program är för långsamt. Hur snabbar jag upp det?¶
Det är en svår fråga, generellt sett. Här är en lista på saker att komma ihåg innan du dyker vidare:
Prestandaegenskaperna varierar mellan olika Python-implementationer. Denna FAQ fokuserar på CPython.
Beteendet kan variera mellan olika operativsystem, särskilt när det gäller I/O eller multi-threading.
Du bör alltid hitta de hetaste punkterna i ditt program innan du försöker optimera någon kod (se modulen
profile
).Genom att skriva benchmark-skript kan du snabbt iterera när du söker efter förbättringar (se modulen
timeit
).Det är starkt rekommenderat att ha god kodtäckning (genom enhetstestning eller någon annan teknik) innan man eventuellt introducerar regressioner som döljs i sofistikerade optimeringar.
Med detta sagt finns det många knep för att snabba upp Python-kod. Här är några allmänna principer som räcker långt för att nå acceptabla prestandanivåer:
Att göra dina algoritmer snabbare (eller byta till snabbare algoritmer) kan ge mycket större fördelar än att försöka strö mikrooptimeringsknep över hela koden.
Använd rätt datastrukturer. Studera dokumentationen för modulen Inbyggda typer och
collections
.När standardbiblioteket tillhandahåller en primitiv metod för att göra något är det troligt (men inte garanterat) att den är snabbare än något alternativ som du kan komma på. Detta gäller i ännu högre grad för primitiver som är skrivna i C, t.ex. inbyggda program och vissa tilläggstyper. Se till exempel till att använda antingen den inbyggda metoden
list.sort()
eller den relaterade funktionensorted()
för att göra sortering (och se Sorteringstekniker för exempel på måttligt avancerad användning).Abstraktioner tenderar att skapa indirektioner och tvinga tolken att arbeta mer. Om nivåerna av indirekta åtgärder uppväger mängden användbart arbete som utförs, blir ditt program långsammare. Du bör undvika överdriven abstraktion, särskilt i form av små funktioner eller metoder (som också ofta är skadliga för läsbarheten).
Om du har nått gränsen för vad ren Python kan tillåta, finns det verktyg som tar dig längre bort. Till exempel kan Cython kompilera en något modifierad version av Python-kod till ett C-tillägg och kan användas på många olika plattformar. Cython kan dra nytta av kompilering (och valfria typannoteringar) för att göra din kod betydligt snabbare än när den tolkas. Om du är säker på dina färdigheter i C-programmering kan du också skriva en C-tilläggsmodul själv.
Se även
Wikisidan som ägnas åt prestandatips.
Vad är det mest effektiva sättet att konkatenera många strängar tillsammans?¶
objekten str
och bytes
är oföränderliga, och därför är det ineffektivt att sammanfoga många strängar eftersom varje sammanfogning skapar ett nytt objekt. I det allmänna fallet är den totala körtidskostnaden kvadratisk i den totala stränglängden.
För att samla många str
-objekt är det rekommenderade idiomet att placera dem i en lista och anropa str.join()
i slutet:
chunks = []
för s i my_strings:
chunks.append(s)
resultat = ''.join(chunks)
(ett annat rimligt effektivt idiom är att använda io.StringIO
)
För att ackumulera många bytes
-objekt är det rekommenderade idiomet att utöka ett bytearray
-objekt med hjälp av konkatenering på plats (operatorn +=
):
resultat = bytearray()
för b i my_bytes_objects:
resultat += b
Sekvenser (Tupler/Listor)¶
Hur konverterar jag mellan tupler och listor?¶
Typkonstruktören tuple(seq)
konverterar vilken sekvens som helst (egentligen vilken iterabel som helst) till en tuple med samma objekt i samma ordning.
Till exempel ger tuple([1, 2, 3])
(1, 2, 3)
och tuple('abc')
ger ('a', 'b', 'c')
. Om argumentet är en tupel görs ingen kopia utan samma objekt returneras, så det är billigt att anropa tuple()
när du inte är säker på att ett objekt redan är en tupel.
Typkonstruktören list(seq)
konverterar en sekvens eller iterabel till en lista med samma objekt i samma ordning. Till exempel ger list((1, 2, 3))
[1, 2, 3]
och list('abc')
ger ['a', 'b', 'c']
. Om argumentet är en lista gör den en kopia precis som seq[:]
skulle göra.
Vad är ett negativt index?¶
Pythonsekvenser är indexerade med positiva och negativa tal. För positiva tal är 0 det första indexet 1 är det andra indexet och så vidare. För negativa index är -1 det sista indexet och -2 är det näst sista indexet och så vidare. Tänk på seq[-n]
som samma sak som seq[len(seq)-n]
.
Att använda negativa index kan vara mycket praktiskt. Till exempel är S[:-1]
hela strängen utom det sista tecknet, vilket är användbart när man vill ta bort den efterföljande nya raden från en sträng.
Hur itererar jag över en sekvens i omvänd ordning?¶
Använd den inbyggda funktionen reversed()
:
för x i omvänd(sekvens):
... # gör något med x ...
Detta påverkar inte din ursprungliga sekvens, men skapar en ny kopia med omvänd ordning som du kan iterera över.
Hur tar man bort dubbletter från en lista?¶
Se Python Cookbook för en lång diskussion om många sätt att göra detta:
Om du inte har något emot att ändra ordning på listan kan du sortera den och sedan skanna från slutet av listan och ta bort dubbletter efter hand:
if mylist:
mylist.sort()
sista = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
annars:
last = mylist[i]
Om alla element i listan kan användas som set-nycklar (dvs. de är alla hashable) är detta ofta snabbare
mylist = list(set(mylist))
Detta omvandlar listan till en uppsättning, varvid dubbletter tas bort, och sedan tillbaka till en lista.
Hur tar du bort flera objekt från en lista¶
Precis som vid borttagning av dubbletter är det en möjlighet att explicit iterera baklänges med ett delete-villkor. Det är dock enklare och snabbare att använda slice replacement med en implicit eller explicit iteration framåt. Här är tre varianter::
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x för x i mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition] (x för x i mylist if keep_condition)
Förståelsen av listan kan vara snabbast.
Hur gör du en matris i Python?¶
Använd en lista:
["detta", 1, "är", "en", "array"]
Listor motsvarar matriser i C eller Pascal när det gäller tidskomplexitet; den främsta skillnaden är att en Python-lista kan innehålla objekt av många olika typer.
Modulen array
tillhandahåller också metoder för att skapa matriser av fasta typer med kompakta representationer, men de är långsammare att indexera än listor. Observera också att NumPy och andra tredjepartspaket också definierar array-liknande strukturer med olika egenskaper.
För att få länkade listor i Lisp-stil kan du emulera cons cells med hjälp av tuples:
lisp_list = ("som", ("detta", ("exempel", None) ) )
Om man vill ha mutabilitet kan man använda listor i stället för tupler. Här är motsvarigheten till en Lisp car lisp_list[0]
och motsvarigheten till cdr är lisp_list[1]
. Gör bara detta om du är säker på att du verkligen behöver det, eftersom det vanligtvis är mycket långsammare än att använda Python-listor.
Hur skapar jag en flerdimensionell lista?¶
Du har säkert försökt skapa en flerdimensionell matris så här:
>>> A = [[None] * 2] * 3
Detta ser korrekt ut om du skriver ut det:
>>> A
[[None, None], [None, None], [None, None]]
Men när du tilldelar ett värde dyker det upp på flera ställen:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Anledningen är att replikering av en lista med *
inte skapar kopior, det skapar bara referenser till de befintliga objekten. Med *3
skapas en lista som innehåller 3 referenser till samma lista med längden två. Ändringar i en rad kommer att visas i alla rader, vilket nästan säkert inte är vad du vill.
Det föreslagna tillvägagångssättet är att först skapa en lista med önskad längd och sedan fylla i varje element med en nyskapad lista:
A = [Ingen] * 3
för i i intervall(3):
A[i] = [Ingen] * 2
Detta genererar en lista som innehåller 3 olika listor med längden två. Du kan också använda en listkomprehension:
w, h = 2, 3
A = [[None] * w for i in range(h)]
Eller så kan du använda ett tillägg som tillhandahåller en matrisdatatyp; NumPy är den mest kända.
Hur tillämpar jag en metod eller funktion på en sekvens av objekt?¶
Att anropa en metod eller funktion och ackumulera returvärdena är en lista, en list comprehension är en elegant lösning:
resultat = [obj.method() för obj i mylist]
resultat = [funktion(obj) för obj i mylist]
Om du bara vill köra metoden eller funktionen utan att spara returvärdena räcker det med en vanlig for
-loop:
för obj i mylist:
obj.metod()
för obj i mylist:
funktion(obj)
Varför ger a_tuple[i] += [’item’] upphov till ett undantag när tillägget fungerar?¶
Detta beror på en kombination av det faktum att utökade tilldelningsoperatorer är tilldelningsoperatorer och skillnaden mellan muterbara och oföränderliga objekt i Python.
Den här diskussionen gäller i allmänhet när utökade tilldelningsoperatorer tillämpas på element i en tupel som pekar på föränderliga objekt, men vi använder en list
och +=
som vårt exempel.
Om du skrev:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Anledningen till undantaget bör vara omedelbart klar: 1
läggs till objektet a_tuple[0]
pekar på (1
), vilket ger resultatobjektet 2
, men när vi försöker tilldela resultatet av beräkningen, 2
, till elementet 0
i tupeln, får vi ett fel eftersom vi inte kan ändra vad ett element i en tuple pekar på.
Under täckmanteln gör den här utökade uppdragsbeskrivningen ungefär så här:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Det är tilldelningsdelen av operationen som ger upphov till felet, eftersom en tuple är oföränderlig.
När du skriver något i stil med:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Undantaget är lite mer överraskande, och ännu mer överraskande är det faktum att även om det fanns ett fel, fungerade tillägget:
>>> a_tuple[0]
['foo', 'item']
För att förstå varför detta händer måste du veta att (a) om ett objekt implementerar en magisk metod __iadd__()
, anropas den när den utökade tilldelningen +=
utförs, och dess returvärde är det som används i tilldelningsuttalandet; och (b) för listor är __iadd__()
likvärdigt med att anropa extend()
på listan och returnera listan. Det är därför vi säger att för listor är +=
en ”kortform” för list.extend()
:
>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]
Detta är likvärdigt med:
>>> result = a_list.__iadd__([1])
>>> a_list = result
Det objekt som a_list pekar på har muterats och pekaren till det muterade objektet tilldelas tillbaka till a_list
. Slutresultatet av tilldelningen är en no-op, eftersom det är en pekare till samma objekt som a_list
tidigare pekade på, men tilldelningen sker ändå.
I vårt tuplexempel är det som händer alltså likvärdigt med:
>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
__iadd__()
lyckas och därmed utökas listan, men även om result
pekar på samma objekt som a_tuple[0]
redan pekar på, resulterar den slutliga tilldelningen fortfarande i ett fel, eftersom tuples är oföränderliga.
Jag vill göra en komplicerad sortering: kan du göra en Schwartzian-transform i Python?¶
Tekniken, som tillskrivs Randal Schwartz från Perl-gemenskapen, sorterar elementen i en lista med en metrisk som mappar varje element till dess ”sorteringsvärde”. I Python använder du argumentet key
för metoden list.sort()
:
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
Hur kan jag sortera en lista efter värden från en annan lista?¶
Slå samman dem till en iterator av tupler, sortera den resulterande listan och välj sedan ut det element du vill ha:
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Objekt¶
Vad är en klass?¶
En klass är den särskilda objekttyp som skapas genom att en class-sats körs. Klassobjekt används som mallar för att skapa instansobjekt, som innehåller både data (attribut) och kod (metoder) som är specifika för en datatyp.
En klass kan baseras på en eller flera andra klasser, som kallas dess basklass(er). Den ärver då attribut och metoder från sina basklasser. Detta gör att en objektmodell successivt kan förfinas genom arv. Du kanske har en generisk klass Mailbox
som tillhandahåller grundläggande åtkomstmetoder för en brevlåda, och underklasser som MboxMailbox
, MaildirMailbox
, OutlookMailbox
som hanterar olika specifika brevlådeformat.
Vad är en metod?¶
En metod är en funktion på något objekt x
som du normalt anropar som x.name(arguments...)
. Metoder definieras som funktioner inuti klassdefinitionen:
klass C:
def meth(self, arg):
return arg * 2 + self.attribute
Vad är jaget?¶
Self är bara ett konventionellt namn för det första argumentet i en metod. En metod som definieras som meth(self, a, b, c)
bör anropas som x.meth(a, b, c)
för någon instans x
av den klass där definitionen förekommer; den anropade metoden kommer att tro att den anropas som meth(x, a, b, c)
.
Se även Varför måste ”self” användas explicit i metoddefinitioner och anrop?.
Hur kontrollerar jag om ett objekt är en instans av en viss klass eller av en underklass av den?¶
Använd den inbyggda funktionen isinstance(obj, cls)
. Du kan kontrollera om ett objekt är en instans av någon av ett antal klasser genom att ange en tupel istället för en enda klass, t.ex. isinstance(obj, (class1, class2, ...))
, och du kan också kontrollera om ett objekt är en av Pythons inbyggda typer, t.ex. isinstance(obj, str)
eller isinstance(obj, (int, float, complex))
.
Observera att isinstance()
även kontrollerar för virtuellt arv från en abstrakt basklass. Så testet kommer att returnera True
för en registrerad klass även om den inte direkt eller indirekt har ärvt från den. För att testa för ”true inheritance”, skanna klassens MRO:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C()
>>> isinstance(c, C) # direct
True
>>> isinstance(c, P) # indirect
True
>>> isinstance(c, Mapping) # virtual
True
# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)
# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False
Observera att de flesta program inte använder isinstance()
på användardefinierade klasser särskilt ofta. Om du utvecklar klasserna själv är en mer korrekt objektorienterad stil att definiera metoder på klasserna som kapslar in ett visst beteende, istället för att kontrollera objektets klass och göra en annan sak baserat på vilken klass det är. Om man t.ex. har en funktion som gör något:
def search(obj):
if isinstance(obj, Brevlåda):
... # kod för att söka i en brevlåda
elif isinstance(obj, Dokument):
... # kod för att söka i ett dokument
elif ...
Ett bättre tillvägagångssätt är att definiera en search()
-metod för alla klasser och bara anropa den:
klass Brevlåda:
def search(self):
... # kod för att söka i en brevlåda
klass Dokument:
def search(self):
... # kod för att söka i ett dokument
obj.search()
Vad är delegering?¶
Delegering är en objektorienterad teknik (även kallad designmönster). Låt oss säga att du har ett objekt x
och vill ändra beteendet hos bara en av dess metoder. Du kan skapa en ny klass som tillhandahåller en ny implementering av den metod du är intresserad av att ändra och delegerar alla andra metoder till motsvarande metod i x
.
Python-programmerare kan enkelt implementera delegering. Till exempel, följande klass implementerar en klass som beter sig som en fil men konverterar all skriven data till versaler:
klass UpperOut:
def __init__(self, outfile):
self._outfile = utfil
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, namn):
return getattr(self._outfile, namn)
Här omdefinierar klassen UpperOut
metoden write()
så att argumentsträngen konverteras till versaler innan den underliggande metoden self._outfile.write()
anropas. Alla andra metoder delegeras till det underliggande objektet self._outfile
. Delegeringen sker via metoden __getattr__()
; se språkreferensen för mer information om hur man kontrollerar attributåtkomst.
Observera att för mer allmänna fall kan delegering bli knepigare. När attribut måste ställas in såväl som hämtas måste klassen definiera en __setattr__()
-metod också, och den måste göra det noggrant. Den grundläggande implementationen av __setattr__()
är ungefär likvärdig med följande:
klass X:
...
def __setattr__(self, namn, värde):
self.__dict__[name] = värde
...
Många __setattr__()
-implementationer anropar object.__setattr__()
för att sätta ett attribut på self utan att orsaka oändlig rekursion:
klass X:
def __setattr__(self, namn, värde):
# Egen logik här...
object.__setattr__(self, namn, värde)
Alternativt är det möjligt att ställa in attribut genom att infoga poster i self.__dict__
direkt.
Hur anropar jag en metod som är definierad i en basklass från en härledd klass som utökar den?¶
Använd den inbyggda funktionen super()
:
klass Avledda(Bas):
def meth(self):
super().meth() # anropar Base.meth
I exemplet kommer super()
automatiskt att bestämma från vilken instans den anropades (värdet self
), leta upp method resolution order (MRO) med type(self).__mro__
och returnera nästa i raden efter Derived
i MRO: Base
.
Hur kan jag organisera min kod för att göra det lättare att ändra basklassen?¶
Du kan tilldela basklassen till ett alias och härleda från aliaset. Då behöver man bara ändra det värde som tilldelats aliaset. För övrigt är detta trick också praktiskt om du vill bestämma dynamiskt (t.ex. beroende på tillgängliga resurser) vilken basklass som ska användas. Exempel:
klass Bas:
...
BaseAlias = Bas
klass Avledda(BaseAlias):
...
Hur skapar jag statiska klassdata och statiska klassmetoder?¶
Både statiska data och statiska metoder (i den mening som avses i C++ eller Java) stöds i Python.
För statiska data definierar du helt enkelt ett klassattribut. För att tilldela ett nytt värde till attributet måste du uttryckligen använda klassnamnet i tilldelningen:
klass C:
count = 0 # antal gånger C.__init__ anropas
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # eller return self.count
c.count
refererar också till C.count
för varje c
så att isinstance(c, C)
gäller, såvida det inte åsidosätts av c
själv eller av någon klass på basklasssökvägen från c.__class__
tillbaka till C
.
Varning: Inom en metod i C skapar en tilldelning som self.count = 42
en ny och orelaterad instans med namnet ”count” i self
egen dict. Ombindning av ett klass-statiskt datanamn måste alltid specificera klassen oavsett om det är inom en metod eller inte:
C.count = 314
Statiska metoder är möjliga:
klass C:
@statiskmetod
def static(arg1, arg2, arg3):
# Ingen parameter 'self'!
...
Ett mycket enklare sätt att få effekten av en statisk metod är dock via en enkel funktion på modulnivå:
def getcount():
returnera C.count
Om din kod är strukturerad så att du definierar en klass (eller en nära relaterad klasshierarki) per modul, ger detta den önskade inkapslingen.
Hur kan jag överbelasta konstruktörer (eller metoder) i Python?¶
Det här svaret gäller egentligen alla metoder, men frågan dyker oftast upp först i samband med konstruktörer.
I C++ skulle du skriva
klass C {
C() { cout << "Inga argument\n"; }
C(int i) { cout << "Argumentet är " << i << "\n"; }
}
I Python måste man skriva en enda konstruktor som fångar upp alla fall med hjälp av standardargument. Till exempel:
klass C:
def __init__(self, i=None):
om i är None:
print("Inga argument")
else:
print("Argumentet är", i)
Detta är inte helt likvärdigt, men tillräckligt nära i praktiken.
Du kan också prova en argumentlista med variabel längd, t.ex.
def __init__(self, *args):
...
Samma tillvägagångssätt fungerar för alla metoddefinitioner.
Jag försöker använda __spam och jag får ett fel om _SomeClassName__spam.¶
Variabelnamn med dubbla inledande understreck ”manglas” för att ge ett enkelt men effektivt sätt att definiera privata klassvariabler. Alla identifierare av formen __spam
(minst två inledande understrykningar, högst en efterföljande understrykning) ersätts textuellt med _classname__spam
, där classname
är det aktuella klassnamnet med eventuella inledande understrykningar borttagna.
Identifieraren kan användas oförändrad inom klassen, men för att komma åt den utanför klassen måste det manglade namnet användas:
klass A:
def __one(self):
returnera 1
def två(self):
return 2 * self.__one()
klass B(A):
def three(self):
return 3 * self._A__one()
four = 4 * A()._A__one()
I synnerhet garanterar detta inte sekretess eftersom en utomstående användare fortfarande avsiktligt kan komma åt det privata attributet; många Python-programmerare bryr sig aldrig om att använda privata variabelnamn alls.
Se även
Specifikationerna för privat namnmangling för detaljer och specialfall.
Min klass definierar __del__ men den anropas inte när jag tar bort objektet.¶
Det finns flera möjliga orsaker till detta.
Satsen del
anropar inte nödvändigtvis __del__()
– den minskar helt enkelt objektets referensantal, och om det når noll anropas __del__()
.
Om dina datastrukturer innehåller cirkulära länkar (t.ex. ett träd där varje barn har en föräldrareferens och varje förälder har en lista med barn) kommer referensantalet aldrig att gå tillbaka till noll. Då och då kör Python en algoritm för att upptäcka sådana cykler, men skräpsamlaren kan köras en tid efter att den sista referensen till din datastruktur försvinner, så din __del__()
-metod kan anropas vid en obekväm och slumpmässig tidpunkt. Detta är obekvämt om du försöker återskapa ett problem. Ännu värre är att ordningen i vilken objektets __del__()
-metoder utförs är godtycklig. Du kan köra gc.collect()
för att tvinga fram en insamling, men det finns patologiska fall där objekt aldrig kommer att samlas in.
Trots cykelinsamlaren är det fortfarande en bra idé att definiera en explicit close()
-metod på objekt som ska anropas när du är klar med dem. Metoden close()
kan sedan ta bort attribut som refererar till underobjekt. Anropa inte __del__()
direkt – __del__()
ska anropa close()
och close()
ska se till att den kan anropas mer än en gång för samma objekt.
Ett annat sätt att undvika cykliska referenser är att använda modulen weakref
, som gör att du kan peka på objekt utan att öka deras referensantal. Träddatastrukturer, till exempel, bör använda svaga referenser för sina föräldra- och syskonreferenser (om de behöver dem!).
Slutligen, om din __del__()
-metod ger upphov till ett undantag, skrivs ett varningsmeddelande ut till sys.stderr
.
Hur får jag en lista över alla instanser av en viss klass?¶
Python håller inte reda på alla instanser av en klass (eller av en inbyggd typ). Du kan programmera klassens konstruktor att hålla reda på alla instanser genom att hålla en lista med svaga referenser till varje instans.
Varför verkar resultatet av id()
inte vara unikt?¶
Inbyggda id()
returnerar ett heltal som garanterat är unikt under objektets livstid. Eftersom detta i CPython är objektets minnesadress händer det ofta att efter att ett objekt har raderats från minnet, allokeras nästa nyskapade objekt på samma position i minnet. Detta illustreras av detta exempel:
>>> id(1000)
13901272
>>> id(2000)
13901272
De två id:na tillhör olika heltalsobjekt som skapas före och raderas omedelbart efter att anropet id()
har utförts. För att vara säker på att objekt vars id du vill undersöka fortfarande är vid liv, skapar du en annan referens till objektet:
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
När kan jag förlita mig på identitetstester med operatorn is?¶
Operatorn is
testar objektets identitet. Testet a is b
är likvärdigt med id(a) == id(b)
.
Den viktigaste egenskapen hos ett identitetstest är att ett objekt alltid är identiskt med sig självt, a is a
returnerar alltid True
. Identitetstester är vanligtvis snabbare än likhetstester. Och till skillnad från likhetstester är identitetstester garanterade att returnera en boolean True
eller False
.
Identitetstester kan dock endast ersättas med likhetstester när objektets identitet är säkerställd. I allmänhet finns det tre omständigheter där identiteten är garanterad:
Tilldelningar skapar nya namn men ändrar inte objektets identitet. Efter tilldelningen
new = old
är det garanterat attnew is old
.Att placera ett objekt i en behållare som lagrar objektreferenser ändrar inte objektets identitet. Efter listtilldelningen
s[0] = x
är det garanterat atts[0] is x
.Om ett objekt är en singleton betyder det att det bara kan finnas en instans av objektet. Efter tilldelningarna
a = None
ochb = None
är det garanterat atta is b
eftersomNone
är en singleton.
Under de flesta andra omständigheter är identitetstester inte tillrådliga och likhetstester är att föredra. I synnerhet bör identitetstester inte användas för att kontrollera konstanter som int
och str
som inte garanterat är singletoner:
>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False
>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False
På samma sätt är nya instanser av mutabla behållare aldrig identiska:
>>> a = []
>>> b = []
>>> a is b
False
I standardbibliotekskoden ser du flera vanliga mönster för korrekt användning av identitetstester:
Som rekommenderas av PEP 8, är ett identitetstest det föredragna sättet att kontrollera för
None
. Detta låter som vanlig engelska i koden och undviker förvirring med andra objekt som kan ha booleska värden som utvärderas till false.Det kan vara svårt att upptäcka valfria argument när
None
är ett giltigt inmatningsvärde. I sådana situationer kan du skapa ett singleton sentinel-objekt som garanterat skiljer sig från andra objekt. Så här kan du till exempel implementera en metod som beter sig somdict.pop()
:_sentinel = objekt() def pop(self, key, default=_sentinel): if nyckel i self: värde = self[nyckel] del self[nyckel] returnera värde om standard är _sentinel: raise KeyError(nyckel) returnera standard
Containerimplementationer behöver ibland komplettera likhetstester med identitetstester. Detta förhindrar att koden blir förvirrad av objekt som
float('NaN')
som inte är lika med sig själva.
Här är till exempel implementeringen av collections.abc.Sequence.__contains__()
:
def __contains__(self, värde):
for v in self:
om v är värde eller v == värde:
return True
returnera Falskt
Hur kan en underklass styra vilka data som lagras i en oföränderlig instans?¶
När du subklassar en oföränderlig typ ska du åsidosätta metoden __new__()
i stället för metoden __init__()
. Den senare körs bara efter att en instans har skapats, vilket är för sent för att ändra data i en oföränderlig instans.
Alla dessa oföränderliga klasser har en annan signatur än sin överordnade klass:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Klasserna kan användas på följande sätt:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
Hur cachar jag metodanrop?¶
De två viktigaste verktygen för att cachelagra metoder är functools.cached_property()
och functools.lru_cache()
. Det förstnämnda lagrar resultat på instansnivå och det sistnämnda på klassnivå.
Metoden cached_property fungerar bara med metoder som inte tar några argument. Den skapar inte någon referens till instansen. Det cachade metodresultatet sparas bara så länge som instansen är vid liv.
Fördelen är att när en instans inte längre används kommer det cachade metodresultatet att släppas direkt. Nackdelen är att om instanser ackumuleras, så kommer även de ackumulerade metodresultaten att göra det. De kan växa utan gräns.
Metoden lru_cache fungerar med metoder som har hashable-argument. Den skapar en referens till instansen om inte särskilda ansträngningar görs för att skicka in svaga referenser.
Fördelen med den senast använda algoritmen är att cacheminnet begränsas av den angivna maxstorleken. Nackdelen är att instanser hålls vid liv tills de åldras ur cacheminnet eller tills cacheminnet rensas.
Detta exempel visar de olika teknikerna:
klass Väder:
"Leta upp väderinformation på en statlig webbplats"
def __init__(self, station_id):
self._station_id = station_id
# _station_id är privat och oföränderligt
def current_temperature(self):
"Senaste timobservationen"
# Cacha inte detta eftersom gamla resultat
# kan vara föråldrade.
@cachad_egenskap
def plats(själv):
"Returnera stationens longitud/latitudkoordinater"
# Resultatet beror bara på station_id
@lru_cache(maxstorlek=20)
def historic_rainfall(self, date, units='mm'):
"Nederbörd på ett givet datum"
# Beror på station_id, datum och enheter.
I exemplet ovan antas att station_id aldrig ändras. Om de relevanta instansattributen är föränderliga kan cached_property-metoden inte fungera eftersom den inte kan upptäcka ändringar av attributen.
För att lru_cache-strategin ska fungera när station_id är föränderligt måste klassen definiera metoderna __eq__()
och __hash__()
så att cachen kan upptäcka relevanta attributuppdateringar:
klass Väder:
"Exempel med en föränderlig stationsidentifierare"
def __init__(self, station_id):
self.station_id = station_id
def change_station(self, station_id):
self.station_id = station_id
def __eq__(self, other):
return self.station_id == other.station_id
def __hash__(self):
return hash(self.station_id)
@lru_cache(maxstorlek=20)
def historic_rainfall(self, date, units='cm'):
"Nederbörd på ett givet datum
# Beror på station_id, datum och enheter.
Moduler¶
Hur skapar jag en .pyc-fil?¶
När en modul importeras för första gången (eller när källfilen har ändrats sedan den aktuella kompilerade filen skapades) ska en fil med namnet .pyc
som innehåller den kompilerade koden skapas i en underkatalog med namnet __pycache__
i katalogen som innehåller filen .py
. Filen .pyc
har ett filnamn som börjar med samma namn som filen .py
och slutar med .pyc
, med en mittdel som beror på den specifika python
-binärfil som skapade den. (Se PEP 3147 för mer information.)
En anledning till att en .pyc
-fil inte skapas kan vara ett behörighetsproblem med katalogen som innehåller källfilen, vilket innebär att underkatalogen __pycache__
inte kan skapas. Detta kan till exempel inträffa om du utvecklar som en användare men kör som en annan, till exempel om du testar med en webbserver.
Om inte miljövariabeln PYTHONDONTWRITEBYTECODE
är inställd, skapas en .pyc-fil automatiskt om du importerar en modul och Python har möjlighet (behörigheter, ledigt utrymme, etc…) att skapa en __pycache__
underkatalog och skriva den kompilerade modulen till den underkatalogen.
Att köra Python på ett skript på högsta nivå betraktas inte som en import och ingen .pyc
kommer att skapas. Om du till exempel har en modul på högsta nivå foo.py
som importerar en annan modul xyz.py
, kommer en .pyc
att skapas för xyz
när du kör foo
(genom att skriva python foo.py
som ett skal-kommando) skapas en .pyc
för xyz
eftersom xyz
importeras, men ingen .pyc
-fil skapas för foo
eftersom foo.py
inte importeras.
Om du behöver skapa en .pyc
-fil för foo
– det vill säga skapa en .pyc
-fil för en modul som inte importeras – kan du göra det med modulerna py_compile
och compileall
.
Modulen py_compile
kan manuellt kompilera valfri modul. Ett sätt är att använda compile()
-funktionen i den modulen interaktivt:
>>> import py_compile
>>> py_compile.compile('foo.py')
Detta kommer att skriva .pyc
till en __pycache__
underkatalog på samma plats som foo.py
(eller så kan du åsidosätta det med den valfria parametern cfile
).
Du kan också automatiskt kompilera alla filer i en katalog eller flera kataloger med modulen compileall
. Du kan göra det från skalprompten genom att köra compileall.py
och ange sökvägen till en katalog som innehåller Python-filer som ska kompileras:
python -m compileall .
Hur hittar jag det aktuella modulnamnet?¶
En modul kan ta reda på sitt eget modulnamn genom att titta på den fördefinierade globala variabeln __name__
. Om denna har värdet '__main__'
körs programmet som ett skript. Många moduler som vanligtvis används genom att importera dem tillhandahåller också ett kommandoradsgränssnitt eller ett självtest, och kör endast denna kod efter att ha kontrollerat __name__
:
def main():
print('Kör test...')
...
if __name__ == '__main__':
main()
Hur kan jag ha moduler som ömsesidigt importerar varandra?¶
Anta att du har följande moduler:
foo.py
:
from bar import bar_var
foo_var = 1
bar.py
:
from foo import foo_var
bar_var = 2
Problemet är att tolken kommer att utföra följande steg:
main imports
foo
Tomma globaler för
foo
skapasfoo
kompileras och börjar exekverasfoo
importsbar
Tomma globaler för
bar
skapasbar
kompileras och börjar exekverasbar
importsfoo
(vilket är en no-op eftersom det redan finns en modul som heterfoo
)Importmekanismen försöker läsa
foo_var
frånfoo
globaler, för att ställa inbar.foo_var = foo.foo_var
Det sista steget misslyckas, eftersom Python inte är klar med tolkningen av foo
ännu och den globala symbolordboken för foo
fortfarande är tom.
Samma sak händer när man använder import foo
och sedan försöker komma åt foo.foo_var
i global kod.
Det finns (minst) tre möjliga lösningar på det här problemet.
Guido van Rossum rekommenderar att man undviker all användning av from <module> import ...
och att all kod placeras i funktioner. Initialiseringar av globala variabler och klassvariabler bör endast använda konstanter eller inbyggda funktioner. Detta innebär att allt från en importerad modul refereras som <module>.<name>
.
Jim Roskind föreslår att du utför stegen i följande ordning i varje modul:
export (globaler, funktioner och klasser som inte behöver importerade basklasser)
import
statementsaktiv kod (inklusive globaler som initieras från importerade värden).
Van Rossum tycker inte så mycket om den här metoden eftersom importen hamnar på ett konstigt ställe, men den fungerar.
Matthias Urlichs rekommenderar att du omstrukturerar din kod så att den rekursiva importen inte är nödvändig i första hand.
Dessa lösningar utesluter inte varandra.
__import__(’x.y.z’) returnerar <modul ’x’>; hur får jag z?¶
Överväg att använda bekvämlighetsfunktionen import_module()
från importlib
istället:
z = importlib.import_module('x.y.z')
När jag redigerar en importerad modul och importerar den på nytt visas inte ändringarna. Varför händer detta?¶
Av både effektivitets- och konsekvensskäl läser Python bara modulfilen första gången en modul importeras. Om den inte gjorde det, i ett program som består av många moduler där var och en importerar samma grundmodul, skulle grundmodulen analyseras och omanalyseras många gånger. Gör så här för att tvinga fram omläsning av en ändrad modul:
import importlib
import modname
importlib.reload(modname)
Varning: denna teknik är inte 100% fool-säker. I synnerhet moduler som innehåller uttalanden som
from modname import some_objects
kommer att fortsätta att fungera med den gamla versionen av de importerade objekten. Om modulen innehåller klassdefinitioner kommer befintliga klassinstanser inte att uppdateras för att använda den nya klassdefinitionen. Detta kan resultera i följande paradoxala beteende:
>>> import importlib
>>> import cls
>>> c = cls.C() # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C) # isinstance is false?!?
False
Problemets natur klargörs om man skriver ut klassobjektens ”identitet”:
>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'