Frågor och svar om design och historia

Varför använder Python indragning för gruppering av uttalanden?

Guido van Rossum anser att det är extremt elegant att använda indragning för gruppering och att det bidrar mycket till tydligheten i det genomsnittliga Python-programmet. De flesta lär sig att älska den här funktionen efter ett tag.

Eftersom det inte finns några start-/slutparenteser kan det inte finnas någon oenighet mellan den gruppering som uppfattas av parsern och den mänskliga läsaren. Ibland kommer C-programmerare att stöta på ett fragment av kod som detta:

if (x <= y)
        x++;
        y--;
z++;

Endast satsen x++ exekveras om villkoret är sant, men indragningen får många att tro något annat. Även erfarna C-programmerare kan ibland stirra länge på koden och undra varför y minskas även om x > y.

Eftersom det inte finns några början/slut-parenteser är Python mycket mindre benäget för konflikter i kodningsstilen. I C finns det många olika sätt att placera parenteserna. När man har vant sig vid att läsa och skriva kod i en viss stil är det normalt att känna sig lite illa till mods när man läser (eller måste skriva) i en annan stil.

Många kodningsstilar placerar start- och slutparenteser på en rad för sig själva. Detta gör programmen betydligt längre och slösar bort värdefullt skärmutrymme, vilket gör det svårare att få en bra överblick över ett program. Helst bör en funktion rymmas på en skärm (säg 20–30 rader). 20 rader Python kan göra mycket mer arbete än 20 rader C. Detta beror inte enbart på avsaknaden av begin/end-parenteser - avsaknaden av deklarationer och datatyperna på hög nivå är också ansvariga - men den indragningsbaserade syntaxen hjälper verkligen till.

Varför får jag konstiga resultat med enkla aritmetiska operationer?

Se nästa fråga.

Varför är flyttalsberäkningar så felaktiga?

Användarna blir ofta överraskade av resultat som detta:

>>> 1.2 - 1.0
0.19999999999999996

och tror att det är en bugg i Python. Men det är det inte. Det har lite att göra med Python och mycket mer att göra med hur den underliggande plattformen hanterar flyttal.

Typen float i CPython använder en C double för lagring. Ett float-objekts värde lagras i binär flyttalsräkning med en fast precision (vanligtvis 53 bitar) och Python använder C-operationer, som i sin tur förlitar sig på maskinvaruimplementeringen i processorn, för att utföra flyttalsoperationer. Detta innebär att när det gäller flyttalsoperationer beter sig Python som många populära språk, inklusive C och Java.

Många tal som enkelt kan skrivas i decimalnotation kan inte uttryckas exakt i binär flyttal. Till exempel efter:

>>> x = 1.2

det lagrade värdet för x är en (mycket god) approximation av decimalvärdet 1.2, men är inte exakt lika med det. På en typisk maskin är det faktiska lagrade värdet:

1.0011001100110011001100110011001100110011001100110011 (binary)

vilket är exakt:

1.1999999999999999555910790149937383830547332763671875 (decimal)

Den typiska precisionen på 53 bitar ger Python floats med 15-16 decimalers noggrannhet.

För en fullständigare förklaring, se floating-point arithmetic kapitlet i Python-handledningen.

Varför är Python-strängar oföränderliga?

Det finns flera fördelar.

Det ena är prestanda: att veta att en sträng är oföränderlig innebär att vi kan allokera utrymme för den vid skapandet och att lagringskraven är fasta och oföränderliga. Detta är också en av anledningarna till skillnaden mellan tupler och listor.

En annan fördel är att strängar i Python betraktas som lika ”elementära” som siffror. Ingen aktivitet kan ändra värdet 8 till något annat, och i Python kan ingen aktivitet ändra strängen ”åtta” till något annat.

Varför måste ”self” användas explicit i metoddefinitioner och anrop?

Idén har lånats från Modula-3. Den visar sig vara mycket användbar av flera olika skäl.

För det första är det mer uppenbart att du använder en metod eller ett instansattribut istället för en lokal variabel. Att läsa self.x eller self.meth() gör det helt klart att en instansvariabel eller metod används även om du inte kan klassdefinitionen utantill. I C++ kan du på sätt och vis se det genom att det inte finns någon lokal variabeldeklaration (förutsatt att globaler är sällsynta eller lätt igenkännliga) - men i Python finns det inga lokala variabeldeklarationer, så du måste slå upp klassdefinitionen för att vara säker. Vissa C++- och Java-kodningsstandarder kräver att instansattribut har ett m_-prefix, så detta uttryck är fortfarande användbart i dessa språk också.

För det andra innebär det att ingen speciell syntax är nödvändig om du uttryckligen vill referera till eller anropa metoden från en viss klass. I C++ måste man använda operatorn :: om man vill använda en metod från en basklass som åsidosätts i en härledd klass - i Python kan man skriva baseclass.methodname(self, <argument list>). Detta är särskilt användbart för __init__()-metoder, och i allmänhet i fall där en härledd klassmetod vill utöka basklassmetoden med samma namn och därmed måste anropa basklassmetoden på något sätt.

Slutligen, för instansvariabler löser det ett syntaktiskt problem med tilldelning: eftersom lokala variabler i Python är (per definition!) de variabler till vilka ett värde tilldelas i en funktionskropp (och som inte uttryckligen förklaras globala), måste det finnas något sätt att berätta för tolken att en tilldelning var avsedd att tilldela en instansvariabel istället för en lokal variabel, och det bör helst vara syntaktiskt (av effektivitetsskäl). C++ gör detta genom deklarationer, men Python har inte deklarationer och det skulle vara synd att behöva införa dem bara för detta ändamål. Att använda den explicita self.var löser detta snyggt. På samma sätt, för användning av instansvariabler, innebär att behöva skriva self.var att referenser till okvalificerade namn i en metod inte behöver söka i instansens kataloger. För att uttrycka det på ett annat sätt lever lokala variabler och instansvariabler i två olika namnrymder, och du måste berätta för Python vilket namnrymd som ska användas.

Varför kan jag inte använda en tilldelning i ett uttryck?

Från och med Python 3.8 kan du göra det!

Tilldelningsuttryck med hjälp av valrossoperatorn := tilldela en variabel i ett uttryck:

while chunk := fp.read(200):
   print(chunk)

Se PEP 572 för mer information.

Varför använder Python metoder för vissa funktioner (t.ex. list.index()) men funktioner för andra (t.ex. len(list))?

Som Guido sa:

(a) För vissa operationer är prefixnotation helt enkelt bättre än postfix – prefix (och infix!) operationer har en lång tradition inom matematiken som gillar notationer där det visuella hjälper matematikern att tänka på ett problem. Jämför hur lätt det är att skriva om en formel som x*(a+b) till x*a + x*b med hur klumpigt det är att göra samma sak med en rå OO-notation.

(b) När jag läser kod som säger len(x) vet jag att den frågar efter längden på något. Detta säger mig två saker: resultatet är ett heltal, och argumentet är någon form av behållare. Tvärtom, när jag läser x.len() måste jag redan veta att x är någon form av container som implementerar ett gränssnitt eller ärver från en klass som har en standard len(). Tänk på den förvirring som ibland uppstår när en klass som inte implementerar en mappning har en get()- eller keys()-metod, eller när något som inte är en fil har en write()-metod.

https://mail.python.org/pipermail/python-3000/2006-November/004643.html

Varför är join() en strängmetod istället för en list- eller tupelmetod?

Strängar blev mycket mer lika andra standardtyper från och med Python 1.6, när metoder lades till som ger samma funktionalitet som alltid har varit tillgänglig med hjälp av funktionerna i strängmodulen. De flesta av dessa nya metoder har blivit allmänt accepterade, men den som verkar få en del programmerare att känna sig obekväma är:

", ".join(['1', '2', '4', '8', '16'])

vilket ger resultatet:

"1, 2, 4, 8, 16"

Det finns två vanliga argument mot denna användning.

Den första går i linje med: ”Det ser riktigt fult ut att använda en metod för en stränglitteral (strängkonstant)”, varpå svaret är att det kan det göra, men en stränglitteral är bara ett fast värde. Om metoderna ska vara tillåtna för namn som är bundna till strängar finns det ingen logisk anledning att göra dem otillgängliga för bokstavliga värden.

Den andra invändningen brukar formuleras som: ”Jag säger verkligen till en sekvens att sammanfoga sina medlemmar med en strängkonstant”. Tyvärr gör du inte det. Av någon anledning verkar det vara mycket mindre svårt att ha split() som en strängmetod, eftersom det i så fall är lätt att se att

"1, 2, 4, 8, 16".split(", ")

är en instruktion till en stränglitual att returnera delsträngarna som avgränsas av den angivna separatorn (eller, som standard, godtyckliga sträckor med vitt utrymme).

join() är en strängmetod eftersom du genom att använda den säger till separatorsträngen att iterera över en sekvens av strängar och infoga sig själv mellan intilliggande element. Den här metoden kan användas med alla argument som följer reglerna för sekvensobjekt, inklusive alla nya klasser som du själv kan definiera. Liknande metoder finns för bytes och bytearray-objekt.

Hur snabba är undantagen?

Ett try/except-block är extremt effektivt om inga undantag uppstår. Att faktiskt fånga ett undantag är dyrt. I versioner av Python före 2.0 var det vanligt att använda detta idiom:

försök:
    värde = mydict[nyckel]
except KeyError:
    mydict[nyckel] = getvalue(nyckel)
    värde = mydict[nyckel]

Detta var bara meningsfullt när du förväntade dig att dict skulle ha nyckeln nästan hela tiden. Om så inte var fallet kodade du det så här:

om nyckel i mydict:
    värde = mydict[nyckel]
annat:
    värde = mydict[nyckel] = getvalue(nyckel)

För detta specifika fall kan du också använda value = dict.setdefault(key, getvalue(key)), men bara om getvalue()-anropet är tillräckligt billigt eftersom det utvärderas i alla fall.

Varför finns det inte en switch- eller case-sats i Python?

I allmänhet utför strukturerade switch-satser ett kodblock när ett uttryck har ett visst värde eller en viss uppsättning värden. Sedan Python 3.10 kan man enkelt matcha bokstavliga värden eller konstanter inom ett namnrymd med en match ... case -sats. Ett äldre alternativ är en sekvens av if... elif... elif... else.

I fall där du måste välja bland ett mycket stort antal möjligheter kan du skapa en ordbok som mappar fallvärden till funktioner som ska anropas. Till exempel:

functions = {'a': funktion_1,
             'b': funktion_2,
             'c': self.method_1}

func = funktioner[värde]
func()

För att anropa metoder på objekt kan du förenkla ytterligare genom att använda den inbyggda getattr() för att hämta metoder med ett visst namn:

klass MyVisitor:
    def visit_a(self):
        ...

    def dispatch(self, värde):
        method_name = 'visit_' + str(värde)
        method = getattr(self, method_name)
        metod()

Vi föreslår att du använder ett prefix för metodnamnen, t.ex. visit_ i det här exemplet. Utan ett sådant prefix skulle en angripare kunna anropa vilken metod som helst på ditt objekt om värdena kommer från en icke betrodd källa.

Att imitera switch med fallthrough, som med C:s switch-case-default, är möjligt, men mycket svårare och mindre nödvändigt.

Kan du inte emulera trådar i tolken istället för att förlita dig på en OS-specifik trådimplementering?

Svar 1: Tyvärr skjuter tolken minst en C-stackram för varje Python-stackram. Dessutom kan tillägg anropa tillbaka till Python vid nästan slumpmässiga tillfällen. Därför kräver en fullständig implementering av trådar trådstöd för C.

Svar 2: Lyckligtvis finns det Stackless Python, som har en helt omdesignad tolkslinga som undviker C-stacken.

Varför kan inte lambda-uttryck innehålla uttalanden?

Lambdauttryck i Python kan inte innehålla satser eftersom Pythons syntaktiska ramverk inte kan hantera satser som är kapslade i uttryck. I Python är detta dock inte ett allvarligt problem. Till skillnad från lambda-former i andra språk, där de lägger till funktionalitet, är Python-lambdas bara en kortfattad notation om du är för lat för att definiera en funktion.

Funktioner är redan förstaklassobjekt i Python och kan deklareras i ett lokalt scope. Därför är den enda fördelen med att använda en lambda istället för en lokalt definierad funktion att du inte behöver hitta på ett namn för funktionen - men det är bara en lokal variabel som funktionsobjektet (som är exakt samma typ av objekt som ett lambdauttryck ger) tilldelas!

Kan Python kompileras till maskinkod, C eller något annat språk?

Cython kompilerar en modifierad version av Python med valfria anteckningar till C-tillägg. Nuitka är en kommande kompilator av Python till C++-kod, med målet att stödja hela Python-språket.

Hur hanterar Python minne?

Detaljerna i Pythons minneshantering beror på implementeringen. Standardimplementationen av Python, CPython, använder referensräkning för att upptäcka otillgängliga objekt och en annan mekanism för att samla in referenscykler genom att periodiskt köra en algoritm för cykeldetektering som letar efter otillgängliga cykler och raderar de inblandade objekten. Modulen gc innehåller funktioner för att utföra en skräpinsamling, få felsökningsstatistik och ställa in insamlarens parametrar.

Andra implementationer (t.ex. Jython eller PyPy) kan dock förlita sig på en annan mekanism, t.ex. en fullfjädrad skräpsamlare. Denna skillnad kan orsaka vissa subtila portningsproblem om din Python-kod är beroende av beteendet hos referensräkningsimplementeringen.

I vissa Python-implementationer kommer följande kod (som fungerar bra i CPython) förmodligen att få slut på filbeskrivare:

för file i very_long_list_of_files:
    f = öppna(fil)
    c = f.read(1)

Genom att använda CPythons referensräkning och destruktorschema stänger varje ny tilldelning till f den föregående filen. Med en traditionell GC kommer dock dessa filobjekt endast att samlas in (och stängas) med varierande och eventuellt långa intervall.

Om du vill skriva kod som fungerar med alla Python-implementationer bör du uttryckligen stänga filen eller använda with-satsen; detta fungerar oavsett minneshanteringsschema:

för fil i very_long_list_of_files:
    med open(file) som f:
        c = f.read(1)

Varför använder inte CPython ett mer traditionellt skräpuppsamlingsschema?

För en sak är detta inte en C-standardfunktion och därför är den inte bärbar. (Ja, vi känner till Boehm GC-biblioteket. Det har bitar av assemblerkod för * de flesta * vanliga plattformar, inte för dem alla, och även om det mestadels är transparent är det inte helt transparent; patchar krävs för att få Python att fungera med det)

Traditionell GC blir också ett problem när Python är inbäddat i andra applikationer. Medan det i ett fristående Python går bra att ersätta standard malloc() och free() med versioner som tillhandahålls av GC-biblioteket, kanske en applikation som bäddar in Python vill ha sitt egna substitut för malloc() och free(), och kanske inte vill ha Pythons. Just nu fungerar CPython med allt som implementerar malloc() och free() på rätt sätt.

Varför frigörs inte allt minne när CPython avslutas?

Objekt som refereras från Python-modulernas globala namnrymder avallokeras inte alltid när Python avslutas. Detta kan hända om det finns cirkulära referenser. Det finns också vissa minnesbitar som allokeras av C-biblioteket som är omöjliga att frigöra (t.ex. kommer ett verktyg som Purify att klaga på dessa). Python är dock aggressiv när det gäller att städa upp minnet vid avslut och försöker förstöra varje enskilt objekt.

Om du vill tvinga Python att ta bort vissa saker vid deallokering, använd modulen atexit för att köra en funktion som tvingar fram dessa borttagningar.

Varför finns det separata datatyper för tuplar och listor?

Listor och tuplar är visserligen lika i många avseenden, men används i allmänhet på fundamentalt olika sätt. Tuples kan liknas vid Pascals ”records” eller C:s ”structures”; de är små samlingar av relaterade data som kan vara av olika typer och som hanteras som en grupp. Till exempel kan en kartesisk koordinat representeras som en tupel av två eller tre tal.

Listor, å andra sidan, är mer som matriser i andra språk. De tenderar att innehålla ett varierande antal objekt som alla har samma typ och som bearbetas ett och ett. Exempelvis returnerar os.listdir('.') en lista med strängar som representerar filerna i den aktuella katalogen. Funktioner som arbetar med denna utdata skulle i allmänhet inte brytas om du lägger till ytterligare en fil eller två i katalogen.

Tuplar är oföränderliga, vilket innebär att när en tupel har skapats kan du inte ersätta något av dess element med ett nytt värde. Listor är mutabla, vilket innebär att du alltid kan ändra en listas element. Endast oföränderliga element kan användas som nycklar i en ordbok, och därför kan endast tuplar och inte listor användas som nycklar.

Hur implementeras listor i CPython?

CPythons listor är egentligen arrayer med variabel längd, inte länkade listor i Lisp-stil. Implementeringen använder en sammanhängande matris med referenser till andra objekt och håller en pekare till denna matris och matrisens längd i en listhuvudstruktur.

Detta gör indexeringen av en lista a[i] till en operation vars kostnad är oberoende av listans storlek eller indexets värde.

När objekt läggs till eller infogas ändras storleken på referensmatrisen. En del smarthet används för att förbättra prestandan för att lägga till objekt upprepade gånger; när matrisen måste växa tilldelas lite extra utrymme så att de närmaste gångerna inte kräver en faktisk storleksändring.

Hur implementeras ordböcker i CPython?

CPythons ordböcker är implementerade som hashtabeller som kan ändras i storlek. Jämfört med B-träd ger detta bättre prestanda för uppslagning (den överlägset vanligaste operationen) under de flesta omständigheter, och implementeringen är enklare.

Dictionaries fungerar genom att beräkna en hashkod för varje nyckel som lagras i dictionariet med hjälp av den inbyggda funktionen hash(). Hashkoden varierar mycket beroende på nyckeln och ett processspecifikt frö; till exempel kan 'Python' hasha till -539294296 medan 'python', en sträng som skiljer sig åt med en enda bit, kan hasha till 1142331976. Hashkoden används sedan för att beräkna en plats i en intern array där värdet kommer att lagras. Om vi antar att du lagrar nycklar som alla har olika hashvärden innebär det att det tar konstant tid - O(1), i Big-O-notation - att hämta en nyckel.

Varför måste nycklar i ordböcker vara oföränderliga?

Implementeringen av hashtabeller för ordböcker använder ett hashvärde som beräknas utifrån nyckelvärdet för att hitta nyckeln. Om nyckeln var ett föränderligt objekt skulle dess värde kunna ändras, och därmed skulle dess hashvärde också kunna ändras. Men eftersom den som ändrar nyckelobjektet inte kan se att det användes som en ordboksnyckel, kan den inte flytta runt posten i ordboken. När du sedan försöker slå upp samma objekt i ordboken kommer det inte att hittas eftersom dess hashvärde är annorlunda. Om du försöker slå upp det gamla värdet kommer det inte heller att hittas, eftersom värdet på det objekt som finns i den hashbins skulle vara annorlunda.

Om du vill ha en ordbok indexerad med en lista konverterar du helt enkelt listan till en tupel först; funktionen tuple(L) skapar en tupel med samma poster som listan L. Tuples är oföränderliga och kan därför användas som nycklar i en ordbok.

Några oacceptabla lösningar som har föreslagits:

  • Hashar listor efter deras adress (objekt-ID). Detta fungerar inte eftersom om du konstruerar en ny lista med samma värde kommer den inte att hittas; t.ex.:

    mydict = {[1, 2]: '12'}
    print(mydict[[1, 2]])
    

    skulle ge upphov till ett KeyError-undantag eftersom id:t för [1, 2] som används i den andra raden skiljer sig från det i den första raden. Med andra ord bör nycklar i ordböcker jämföras med hjälp av ==, inte med hjälp av is.

  • Gör en kopia när du använder en lista som nyckel. Det här fungerar inte eftersom listan, som är ett föränderligt objekt, kan innehålla en referens till sig själv, och då skulle kopieringskoden hamna i en oändlig loop.

  • Tillåt listor som nycklar men säg till användaren att inte ändra dem. Detta skulle möjliggöra en klass av svårspårade buggar i program när du glömmer eller modifierar en lista av misstag. Det ogiltigförklarar också en viktig invariant för ordböcker: varje värde i d.keys() kan användas som en nyckel i ordboken.

  • Markera listor som skrivskyddade när de används som en nyckel i en ordbok. Problemet är att det inte bara är objektet på den översta nivån som kan ändra sitt värde; du kan använda en tupel som innehåller en lista som nyckel. Om du anger något som nyckel i en ordbok måste du markera alla objekt som kan nås därifrån som skrivskyddade - och återigen kan självrefererande objekt orsaka en oändlig slinga.

Det finns ett trick för att komma runt detta om du behöver, men använd det på egen risk: Du kan linda in en föränderlig struktur i en klassinstans som har både en __eq__()- och en __hash__()-metod. Du måste då se till att hashvärdet för alla sådana omslutningsobjekt som finns i en ordbok (eller annan hashbaserad struktur) förblir fixerat medan objektet finns i ordboken (eller annan struktur):

klass ListWrapper:
    def __init__(self, the_list):
        self.the_list = the_list

    def __eq__(self, other):
        return self.the_list == other.the_list

    def __hash__(self):
        l = self.the_list
        resultat = 98767 - len(l)*555
        för i, el i enumerate(l):
            försök:
                resultat = resultat + (hash(el) % 9999999) * 1001 + i
            utom Undantag:
                resultat = (resultat % 7777777) + i * 333
        returnera resultat

Observera att hashberäkningen kompliceras av möjligheten att vissa medlemmar i listan inte kan hashas och även av möjligheten till aritmetiskt överflöd.

Dessutom måste det alltid vara så att om o1 == o2 (dvs. o1.__eq__(o2) is True) så är hash(o1) == hash(o2) (dvs. o1.__hash__() == o2.__hash__()), oavsett om objektet finns i en ordbok eller inte. Om du inte uppfyller dessa restriktioner kommer ordböcker och andra hashbaserade strukturer att bete sig illa.

När det gäller ListWrapper får den omslutna listan inte ändras när omslutningsobjektet finns i en ordbok för att undvika anomalier. Gör inte detta om du inte är beredd att fundera ordentligt över kraven och konsekvenserna av att inte uppfylla dem på rätt sätt. Betrakta dig själv som varnad.

Varför returnerar inte list.sort() den sorterade listan?

I situationer där prestanda är viktigt skulle det vara slöseri att göra en kopia av listan bara för att sortera den. Därför sorterar list.sort() listan på plats. För att påminna dig om det faktumet returnerar den inte den sorterade listan. På så sätt blir du inte lurad att av misstag skriva över en lista när du behöver en sorterad kopia men också behöver behålla den osorterade versionen.

Om du vill returnera en ny lista använder du istället den inbyggda funktionen sorted(). Denna funktion skapar en ny lista från en tillhandahållen iterabel, sorterar den och returnerar den. Så här gör du till exempel för att iterera över nycklarna i en ordbok i sorterad ordning:

för nyckel i sorterad(mydict):
    ...  # gör vad som helst med mydict[key] ...

Hur specificerar och verkställer du en gränssnittsspecifikation i Python?

En gränssnittsspecifikation för en modul, som tillhandahålls av språk som C++ och Java, beskriver prototyperna för modulens metoder och funktioner. Många anser att tillämpning av gränssnittsspecifikationer under kompileringstiden underlättar konstruktionen av stora program.

Python 2.6 lägger till en abc-modul som låter dig definiera abstrakta basklasser (ABC). Du kan sedan använda isinstance() och issubclass() för att kontrollera om en instans eller en klass implementerar en viss ABC. Modulen collections.abc definierar en uppsättning användbara ABC:er som Iterable, Container och MutableMapping.

För Python kan många av fördelarna med gränssnittsspecifikationer uppnås genom en lämplig testdisciplin för komponenter.

En bra testsvit för en modul kan både tillhandahålla ett regressionstest och fungera som en specifikation för modulgränssnittet och en uppsättning exempel. Många Python-moduler kan köras som ett skript för att ge ett enkelt ”självtest” Även moduler som använder komplexa externa gränssnitt kan ofta testas isolerat med hjälp av triviala ”stub”-emuleringar av det externa gränssnittet. Modulerna doctest och unittest eller testramverk från tredje part kan användas för att konstruera uttömmande testsviter som testar varje kodrad i en modul.

En lämplig testdisciplin kan hjälpa till att bygga stora komplexa applikationer i Python lika bra som att ha gränssnittsspecifikationer. Faktum är att det kan vara bättre eftersom en gränssnittsspecifikation inte kan testa vissa egenskaper hos ett program. Till exempel förväntas list.append()-metoden lägga till nya element i slutet av en intern lista; en gränssnittsspecifikation kan inte testa att din list.append()-implementering faktiskt gör detta korrekt, men det är trivialt att kontrollera den här egenskapen i en testsvit.

Att skriva testsviter är till stor hjälp, och du kanske vill utforma din kod så att den blir lätt att testa. En alltmer populär teknik, testdriven utveckling, innebär att man skriver delar av testsviten först, innan man skriver någon av den faktiska koden. Med Python kan du naturligtvis vara slarvig och inte skriva testfall alls.

Varför finns det ingen goto?

På 1970-talet insåg man att obegränsad goto kunde leda till rörig ”spaghetti”-kod som var svår att förstå och revidera. I ett högnivåspråk är det också onödigt så länge det finns sätt att förgrena (i Python, med if-satser och or, and och if/else-uttryck) och loopa (med while och for-satser, som eventuellt innehåller continue och break).

Man kan också använda undantag för att tillhandahålla ett ”strukturerat goto” som fungerar även över funktionsanrop. Många anser att undantag på ett bekvämt sätt kan emulera alla rimliga användningar av go eller goto-konstruktionerna i C, Fortran och andra språk. Till exempel:

class label(Exception): pass # deklarera en etikett

try:
    ...
    if villkor: raise label() # goto label
    ...
except label:  # vart ska man gå
    passera
...

Detta gör att du inte kan hoppa in i mitten av en slinga, men det brukar ändå betraktas som ett missbruk av goto. Använd sparsamt.

Varför kan inte råsträngar (r-strängar) sluta med ett backslash?

Mer exakt kan de inte sluta med ett udda antal backslash: den oparade backslashen i slutet undgår det avslutande citattecknet och lämnar en oavslutad sträng.

Råa strängar utformades för att underlätta skapandet av indata för processorer (främst motorer för reguljära uttryck) som vill göra sin egen backslash escape-bearbetning. Sådana processorer anser att en omatchad efterföljande backslash ändå är ett fel, så råa strängar tillåter inte det. I gengäld tillåter de att du skickar vidare strängens citattecken genom att escapa det med en backslash. Dessa regler fungerar bra när r-strängar används för sitt avsedda ändamål.

Om du försöker skapa Windows-sökvägar bör du notera att alla Windows-anrop också accepterar snedstreck:

f = open("/mydir/file.txt") # fungerar bra!

Om du försöker skapa ett sökvägsnamn för ett DOS-kommando, prova t.ex. en av

dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\my\\dos\\dir\\"

Varför har Python inte ett ”with”-uttalande för attributtilldelningar?

Python har en with-sats som omsluter exekveringen av ett block och anropar kod vid ingången till och utgången från blocket. Vissa språk har en konstruktion som ser ut så här:

med obj:
    a = 1 # likvärdig med obj.a = 1
    total = total + 1 # obj.total = obj.total + 1

I Python skulle en sådan konstruktion vara tvetydig.

Andra språk, som Object Pascal, Delphi och C++, använder statiska typer, så det är möjligt att på ett otvetydigt sätt veta vilken medlem som tilldelas. Detta är huvudpoängen med statisk typning - kompilatorn vet alltid omfattningen av varje variabel vid kompileringstillfället.

Python använder dynamiska typer. Det är omöjligt att i förväg veta vilket attribut som kommer att refereras till vid körning. Medlemsattribut kan läggas till eller tas bort från objekt i farten. Detta gör det omöjligt att, genom en enkel läsning, veta vilket attribut som refereras: ett lokalt, ett globalt eller ett medlemsattribut?

Ta till exempel följande ofullständiga utdrag:

def foo(a):
    med a:
        print(x)

I utdraget antas att a måste ha ett medlemsattribut som heter x. Det finns dock inget i Python som säger detta till tolken. Vad skulle hända om a är, låt oss säga, ett heltal? Om det finns en global variabel som heter x, kommer den att användas inuti with-blocket? Som du ser gör Pythons dynamiska natur sådana val mycket svårare.

Den primära fördelen med with och liknande språkfunktioner (minskning av kodvolymen) kan dock lätt uppnås i Python genom tilldelning. Istället för:

funktion(args).mydict[index][index].a = 21
funktion(args).mydict[index][index].b = 42
funktion(args).mydict[index][index].c = 63

skriv detta:

ref = funktion(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63

Detta har också den bieffekten att körhastigheten ökar eftersom namnbindningar löses vid körning i Python, och den andra versionen behöver bara utföra upplösningen en gång.

Liknande förslag om att införa syntax för att ytterligare minska kodvolymen, t.ex. genom att använda en ”ledande punkt”, har avvisats till förmån för explicitet (se https://mail.python.org/pipermail/python-ideas/2016-May/040070.html).

Varför stöder inte generatorer uttalandet med?

Av tekniska skäl skulle en generator som används direkt som en kontexthanterare inte fungera korrekt. När, vilket är vanligast, en generator används som en iterator som körs till slut, behövs ingen stängning. När så är fallet, skriv in det som contextlib.closing(generator) i with-satsen.

Varför krävs kolon för if/while/def/class-satserna?

Kolonet är nödvändigt främst för att öka läsbarheten (ett av resultaten av det experimentella ABC-språket). Tänk på detta:

om a == b
    skriv ut(a)

mot

om a == b:
    print(a)

Lägg märke till att den andra är något lättare att läsa. Lägg också märke till att exemplet i det här FAQ-svaret avslutas med ett kolon; det är ett vanligt sätt att använda ord på engelska.

Ett annat mindre skäl är att kolon gör det lättare för redaktörer med syntaxmarkering; de kan leta efter kolon för att avgöra när indragningen behöver ökas istället för att behöva göra en mer omfattande analys av programtexten.

Varför tillåter Python kommatecken i slutet av listor och tupler?

I Python kan du lägga till ett efterföljande kommatecken i slutet av listor, tupler och ordböcker:

[1, 2, 3,]
('a', 'b', 'c',)
d = {
    "A": [1, 5],
    "B": [6, 7], # sista efterföljande kommatecknet är valfritt men bra stil
}

Det finns flera skäl att tillåta detta.

När du har ett bokstavligt värde för en lista, tupel eller ordbok utspritt på flera rader är det lättare att lägga till fler element eftersom du inte behöver komma ihåg att lägga till ett kommatecken på föregående rad. Raderna kan också ordnas om utan att det uppstår ett syntaxfel.

Om du råkar utelämna kommatecknet kan det leda till fel som är svåra att diagnostisera. Till exempel:

x = [
  "avgift",
  "fie"
  "foo",
  "fum"
]

Den här listan ser ut att ha fyra element, men den innehåller i själva verket tre: ”fee”, ”fiefoo” och ”fum”. Genom att alltid lägga till ett kommatecken undviker man denna felkälla.

Att tillåta efterföljande kommatecken kan också göra det lättare att generera programmatisk kod.