9. Klasser¶
Klasser är ett sätt att paketera data och funktionalitet tillsammans. Genom att skapa en ny klass skapas en ny typ av objekt, vilket gör det möjligt att skapa nya instanser av den typen. Varje klassinstans kan ha attribut kopplade till sig för att bibehålla sitt tillstånd. Klassinstanser kan också ha metoder (definierade av klassen) för att ändra sitt tillstånd.
Jämfört med andra programmeringsspråk lägger Pythons klassmekanism till klasser med ett minimum av ny syntax och semantik. Det är en blandning av de klassmekanismer som finns i C++ och Modula-3. Python-klasser har alla standardfunktioner för objektorienterad programmering: klassens arvsmekanism tillåter flera basklasser, en härledd klass kan åsidosätta alla metoder i sin basklass eller sina basklasser och en metod kan anropa metoden i en basklass med samma namn. Objekt kan innehålla godtyckliga mängder och typer av data. Precis som för moduler är klasser en del av Pythons dynamiska natur: de skapas under körning och kan modifieras ytterligare efter skapandet.
I C++-terminologi är klassmedlemmar (inklusive datamedlemmar) normalt public (utom se nedan Privata variabler) och alla medlemsfunktioner är virtuella. Precis som i Modula-3 finns det inga kortkommandon för att referera till objektets medlemmar från dess metoder: metodfunktionen deklareras med ett explicit första argument som representerar objektet, vilket tillhandahålls implicit av anropet. Precis som i Smalltalk är klasserna i sig själva objekt. Detta ger semantik för import och namnändring. Till skillnad från C++ och Modula-3 kan inbyggda typer användas som basklasser för utvidgning av användaren. Precis som i C++ kan de flesta inbyggda operatorer med speciell syntax (aritmetiska operatorer, subskription etc.) omdefinieras för klassinstanser.
(I brist på allmänt accepterad terminologi för att tala om klasser kommer jag ibland att använda Smalltalk- och C++-termer. Jag skulle använda Modula-3-termer, eftersom dess objektorienterade semantik ligger närmare Python än C++, men jag förväntar mig att få läsare har hört talas om det)
9.1. Några ord om namn och objekt¶
Objekten är individuella och flera namn (i flera scopes) kan bindas till samma objekt. Detta är känt som aliasing i andra språk. Detta uppskattas vanligtvis inte vid en första anblick av Python, och kan säkert ignoreras när man hanterar oföränderliga bastyper (tal, strängar, tupler). Aliasing har dock en möjligen överraskande effekt på semantiken i Python-kod som involverar föränderliga objekt som listor, ordböcker och de flesta andra typer. Detta används vanligtvis till förmån för programmet, eftersom alias beter sig som pekare i vissa avseenden. Till exempel är det billigt att skicka ett objekt eftersom endast en pekare skickas av implementationen; och om en funktion ändrar ett objekt som skickas som ett argument kommer den som anropar att se förändringen — detta eliminerar behovet av två olika mekanismer för argumentpassning som i Pascal.
9.2. Python Scopes och namnrymder¶
Innan jag introducerar klasser måste jag först berätta något om Pythons scope-regler. Klassdefinitioner spelar några snygga trick med namnrymder, och du måste veta hur scopes och namnrymder fungerar för att förstå vad som händer. För övrigt är kunskap om detta ämne användbart för alla avancerade Python-programmerare.
Låt oss börja med några definitioner.
En namnrymd är en mappning från namn till objekt. De flesta namnrymder är för närvarande implementerade som Python-ordlistor, men det är normalt inte märkbart på något sätt (förutom prestanda), och det kan ändras i framtiden. Exempel på namnrymder är: uppsättningen inbyggda namn (som innehåller funktioner som abs()
och inbyggda namn på undantag), de globala namnen i en modul och de lokala namnen i en funktionsinstruktion. På sätt och vis utgör också uppsättningen av attribut för ett objekt ett namnrymd. Det viktiga att veta om namnrymder är att det inte finns någon som helst relation mellan namn i olika namnrymder; till exempel kan två olika moduler båda definiera en funktion maximize
utan förvirring — användare av modulerna måste prefixera den med modulnamnet.
Förresten, jag använder ordet attribut för alla namn som följer efter en punkt — till exempel, i uttrycket z.real
är real
ett attribut för objektet z
. I strikt mening är referenser till namn i moduler attributreferenser: i uttrycket modname.funcname
är modname
ett modulobjekt och funcname
är ett attribut till det. I det här fallet råkar det finnas en enkel mappning mellan modulens attribut och de globala namn som definieras i modulen: de delar samma namnrymd! [1]
Attributen kan vara skrivskyddade eller skrivbara. I det senare fallet är det möjligt att tilldela attribut. Modulattribut är skrivbara: du kan skriva modname.the_answer = 42
. Skrivbara attribut kan också tas bort med del
. Till exempel, del modname.the_answer
kommer att ta bort attributet the_answer
från objektet som heter modname
.
Namnrymder skapas vid olika tidpunkter och har olika livslängd. Namnrymden som innehåller de inbyggda namnen skapas när Python-tolken startar och raderas aldrig. Det globala namnrymden för en modul skapas när moduldefinitionen läses in; normalt varar modulnamnrymder också tills tolken avslutas. De satser som utförs av tolkens anrop på toppnivå, antingen inlästa från en skriptfil eller interaktivt, betraktas som en del av en modul som heter __main__
, och de har därför ett eget globalt namnrymd. (De inbyggda namnen finns faktiskt också i en modul; denna kallas builtins
)
Det lokala namnrymden för en funktion skapas när funktionen anropas och raderas när funktionen returneras eller ger upphov till ett undantag som inte hanteras inom funktionen. (Egentligen skulle ”glömma” vara ett bättre sätt att beskriva vad som faktiskt händer) Naturligtvis har rekursiva anrop alla sina egna lokala namnrymder.
En scope är en textuell region i ett Python-program där en namnrymd är direkt åtkomlig. ”Direkt åtkomlig” betyder här att en okvalificerad referens till ett namn försöker hitta namnet i namnrymden.
Även om scopes bestäms statiskt används de dynamiskt. När som helst under exekveringen finns det 3 eller 4 nästlade scopes vars namnrymder är direkt åtkomliga:
det innersta omfånget, som söks först, innehåller de lokala namnen
scopen för alla inneslutande funktioner, som genomsöks med början från närmaste inneslutande scope, innehåller icke-lokala, men även icke-globala namn
det näst sista omfånget innehåller den aktuella modulens globala namn
det yttersta omfånget (söks sist) är namnrymden som innehåller inbyggda namn
Om ett namn deklareras som globalt, går alla referenser och tilldelningar direkt till det näst sista scope som innehåller modulens globala namn. För att återbinda variabler som finns utanför det innersta scopet kan nonlocal
användas; om de inte deklareras som icke-lokala är dessa variabler skrivskyddade (ett försök att skriva till en sådan variabel skapar helt enkelt en ny lokal variabel i det innersta scopet, medan den identiskt namngivna yttre variabeln lämnas oförändrad).
Vanligtvis refererar det lokala omfånget till de lokala namnen på den (textuellt) aktuella funktionen. Utanför funktioner refererar det lokala omfånget till samma namnrymd som det globala omfånget: modulens namnrymd. Klassdefinitioner placerar ytterligare en namnrymd i det lokala omfånget.
Det är viktigt att inse att omfattningar bestäms textuellt: den globala omfattningen av en funktion som definieras i en modul är den modulens namnrymd, oavsett varifrån eller med vilket alias funktionen anropas. Å andra sidan görs den faktiska sökningen efter namn dynamiskt, vid körning — språkdefinitionen utvecklas dock mot statisk namnupplösning, vid ”kompileringstid”, så lita inte på dynamisk namnupplösning! (Faktum är att lokala variabler redan bestäms statiskt)
En speciell finess med Python är att – om inget global
eller nonlocal
statement är i kraft – assignments till namn alltid går in i det innersta scope. Tilldelningar kopierar inte data — de binder bara namn till objekt. Detsamma gäller för borttagningar: uttalandet del x
tar bort bindningen av x
från det namnrymd som refereras av det lokala scopet. Faktum är att alla operationer som introducerar nya namn använder det lokala omfånget: i synnerhet import
-satser och funktionsdefinitioner binder modul- eller funktionsnamnet i det lokala omfånget.
Satsen global
kan användas för att ange att vissa variabler finns i det globala omfånget och bör återföras dit; satsen nonlocal
anger att vissa variabler finns i ett omslutande omfång och bör återföras dit.
9.2.1. Exempel på scopes och namnrymder¶
Detta är ett exempel som visar hur man refererar till olika scopes och namnrymder, och hur global
och nonlocal
påverkar variabelbindning:
def scope_test():
def do_local():
spam = "lokal spam"
def do_nonlocal():
icke-lokalt skräppost
spam = "icke-lokalt skräppost"
def do_global():
global skräppost
spam = "global skräppost"
spam = "test spam"
do_local()
print("Efter lokal tilldelning:", spam)
do_nonlocal()
print("Efter icke-lokal tilldelning:", spam)
do_global()
print("Efter global tilldelning:", spam)
scope_test()
print("I global omfattning:", spam)
Utmatningen av exempelkoden är:
Efter lokalt uppdrag: test spam
Efter icke-lokalt uppdrag: icke-lokalt spam
Efter global tilldelning: icke-lokalt spam
I global omfattning: global skräppost
Observera att local-tilldelningen (som är standard) inte ändrade scope_test:s bindning av spam. Tilldelningen nonlocal
ändrade scope_test:s bindning av spam, och tilldelningen global
ändrade bindningen på modulnivå.
Du kan också se att det inte fanns någon tidigare bindning för spam före tilldelningen global
.
9.3. En första titt på klasserna¶
Classes introducerar lite ny syntax, tre nya objekttyper och en del ny semantik.
9.3.1. Klassdefinition Syntax¶
Den enklaste formen av klassdefinition ser ut så här:
klass Klassnamn:
<statement-1>
.
.
.
<statement-N>
Klassdefinitioner måste, precis som funktionsdefinitioner (def
-satser), exekveras innan de får någon effekt. (Man skulle kunna tänka sig att placera en klassdefinition i en gren av en if
-sats, eller inuti en funktion)
I praktiken är satserna i en klassdefinition oftast funktionsdefinitioner, men andra satser är tillåtna och ibland användbara — vi återkommer till detta senare. Funktionsdefinitionerna i en klass har normalt en speciell form av argumentlista, dikterad av anropskonventionerna för metoder — återigen, detta förklaras senare.
När en klassdefinition skrivs in skapas ett nytt namnrymd som används som det lokala området — alla tilldelningar till lokala variabler går alltså till detta nya namnrymd. I synnerhet funktionsdefinitioner binder namnet på den nya funktionen här.
När en klassdefinition lämnas på normalt sätt (via slutet) skapas ett klassobjekt. Detta är i princip ett omslag runt innehållet i det namnrymd som skapats av klassdefinitionen; vi lär oss mer om klassobjekt i nästa avsnitt. Det ursprungliga lokala omfånget (det som gällde precis innan klassdefinitionen skrevs in) återställs och klassobjektet binds här till det klassnamn som anges i klassdefinitionens rubrik (ClassName
i exemplet).
9.3.2. Klassobjekt¶
Klassobjekt stöder två typer av operationer: attributreferenser och instansiering.
Attributreferenser använder den standardsyntax som används för alla attributreferenser i Python: obj.name
. Giltiga attributnamn är alla de namn som fanns i klassens namnrymd när klassobjektet skapades. Så, om klassdefinitionen såg ut så här:
klass MyClass:
"""En enkel exempelklass"""
i = 12345
def f(self):
return 'hej världen'
så är MyClass.i
och MyClass.f
giltiga attributreferenser, som returnerar ett heltal respektive ett funktionsobjekt. Klassattribut kan också tilldelas, så du kan ändra värdet på MyClass.i
genom tilldelning. __doc__
är också ett giltigt attribut, som returnerar den dokumentsträng som tillhör klassen: "A simple example class"
.
Klass instantiering använder funktionsnotation. Låtsas bara att klassobjektet är en parameterlös funktion som returnerar en ny instans av klassen. Till exempel (med antagande av ovanstående klass):
x = MyClass()
skapar en ny instans av klassen och tilldelar detta objekt till den lokala variabeln x
.
Instantiering (”anrop” av ett klassobjekt) skapar ett tomt objekt. Många klasser vill skapa objekt med instanser som är anpassade till ett visst initialt tillstånd. Därför kan en klass definiera en speciell metod med namnet __init__()
, så här:
def __init__(self):
self.data = []
När en klass definierar en __init__()
-metod, anropar klassinstantiering automatiskt __init__()
för den nyskapade klassinstansen. Så i det här exemplet kan en ny, initialiserad instans erhållas genom att:
x = MyClass()
Naturligtvis kan metoden __init__()
ha argument för större flexibilitet. I så fall skickas de argument som ges till operatorn för klassinstantiering vidare till __init__()
. Till exempel
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
9.3.3. Instansobjekt¶
Vad kan vi nu göra med instansobjekt? De enda operationer som förstås av instansobjekt är attributreferenser. Det finns två typer av giltiga attributnamn: dataattribut och metoder.
Dataattribut motsvarar ”instansvariabler” i Smalltalk och ”datamedlemmar” i C++. Dataattribut behöver inte deklareras; precis som lokala variabler uppstår de när de först tilldelas. Om till exempel x
är instansen av MyClass
som skapades ovan, kommer följande kod att skriva ut värdet 16
, utan att lämna några spår:
x.counter = 1
medan x.counter < 10:
x.räknare = x.räknare * 2
print(x.räknare)
radera x.räknare
Den andra typen av instansattributreferens är en metod. En metod är en funktion som ”hör till” ett objekt.
Giltiga metodnamn för ett instansobjekt beror på dess klass. Per definition definierar alla attribut för en klass som är funktionsobjekt motsvarande metoder för dess instanser. Så i vårt exempel är x.f
en giltig metodreferens, eftersom MyClass.f
är en funktion, men x.i
är det inte, eftersom MyClass.i
inte är det. Men x.f
är inte samma sak som MyClass.f
— det är ett metodobjekt, inte ett funktionsobjekt.
9.3.4. Metod Objekt¶
Vanligtvis anropas en metod direkt efter att den har bundits:
x.f()
Om x = MyClass()
, som ovan, returnerar detta strängen 'hello world'
. Det är dock inte nödvändigt att anropa en metod direkt: x.f
är ett metodobjekt och kan sparas och anropas senare. Till exempel:
xf = x.f
while True:
print(xf())
kommer att fortsätta att skriva ut ”Hello World” till tidens ände.
Vad händer egentligen när en metod anropas? Du kanske har lagt märke till att x.f()
anropades utan argument ovan, trots att funktionsdefinitionen för f()
angav ett argument. Vad hände med argumentet? Python gör väl ett undantag när en funktion som kräver ett argument anropas utan något — även om argumentet faktiskt inte används…
Egentligen har du kanske gissat svaret: det speciella med metoder är att instansobjektet skickas som funktionens första argument. I vårt exempel är anropet x.f()
exakt likvärdigt med MyClass.f(x)
. I allmänhet är anrop av en metod med en lista med n argument likvärdigt med anrop av motsvarande funktion med en argumentlista som skapas genom att infoga metodens instansobjekt före det första argumentet.
I allmänhet fungerar metoder på följande sätt. När ett icke-dataattribut för en instans refereras, söks instansens klass. Om namnet anger ett giltigt klassattribut som är ett funktionsobjekt, packas referenser till både instansobjektet och funktionsobjektet in i ett metodobjekt. När metodobjektet anropas med en argumentlista, konstrueras en ny argumentlista av instansobjektet och argumentlistan, och funktionsobjektet anropas med denna nya argumentlista.
9.3.5. Klass- och instansvariabler¶
Generellt sett är instansvariabler för data som är unika för varje instans och klassvariabler för attribut och metoder som delas av alla instanser av klassen:
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
Som diskuterats i Några ord om namn och objekt kan delade data ha överraskande effekter när det gäller mutable-objekt som listor och ordböcker. Till exempel bör listan tricks i följande kod inte användas som en klassvariabel eftersom bara en enda lista skulle delas av alla Dog-instanser:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
Korrekt design av klassen bör använda en instansvariabel istället:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
9.4. Slumpmässiga kommentarer¶
Om samma attributnamn förekommer både i en instans och i en klass, prioriterar attributuppslagningen instansen:
>>> class Warehouse:
... purpose = 'storage'
... region = 'west'
...
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
Dataattribut kan refereras till av metoder såväl som av vanliga användare (”klienter”) av ett objekt. Med andra ord är klasser inte användbara för att implementera rena abstrakta datatyper. Faktum är att ingenting i Python gör det möjligt att genomdriva datahölje — allt är baserat på konvention. (Å andra sidan kan Python-implementationen, skriven i C, helt dölja implementationsdetaljer och kontrollera åtkomst till ett objekt om det behövs; detta kan användas av tillägg till Python skrivna i C.)
Klienter bör använda dataattribut med försiktighet — klienter kan förstöra invarianter som upprätthålls av metoderna genom att stämpla på sina dataattribut. Observera att klienter kan lägga till egna dataattribut till ett instansobjekt utan att påverka metodernas giltighet, så länge namnkonflikter undviks — återigen kan en namngivningskonvention spara mycket huvudvärk här.
Det finns ingen förkortning för att referera till dataattribut (eller andra metoder!) inom metoder. Jag tycker att detta faktiskt ökar metodernas läsbarhet: det finns ingen risk för att man blandar ihop lokala variabler och instansvariabler när man tittar igenom en metod.
Ofta kallas det första argumentet i en metod för self
. Detta är inget annat än en konvention: namnet self
har absolut ingen speciell betydelse för Python. Observera dock att om du inte följer konventionen kan din kod vara mindre läsbar för andra Python-programmerare, och det är också tänkbart att ett class browser-program kan skrivas som förlitar sig på en sådan konvention.
Varje funktionsobjekt som är ett klassattribut definierar en metod för instanser av den klassen. Det är inte nödvändigt att funktionsdefinitionen är textuellt innesluten i klassdefinitionen: att tilldela ett funktionsobjekt till en lokal variabel i klassen är också ok. Till exempel:
# Funktion definierad utanför klassen
def f1(self, x, y):
return min(x, x+y)
klassen C:
f = f1
def g(self):
return 'hej världen'
h = g
Nu är f
, g
och h
alla attribut av klassen C
som refererar till funktionsobjekt, och följaktligen är de alla metoder för instanser av C
— h
är exakt ekvivalent med g
. Notera att denna praxis oftast bara tjänar till att förvirra läsaren av ett program.
Metoder kan anropa andra metoder genom att använda metodattribut för argumentet self
:
klass Väska:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
Metoder kan referera till globala namn på samma sätt som vanliga funktioner. Det globala scope som associeras med en metod är den modul som innehåller dess definition. (En klass används aldrig som globalt scope.) Även om det sällan finns någon bra anledning att använda globala data i en metod, finns det många legitima användningsområden för det globala scopet: För det första kan funktioner och moduler som importeras till det globala scopet användas av metoder, liksom funktioner och klasser som definieras i det. Vanligtvis är klassen som innehåller metoden själv definierad i detta globala scope, och i nästa avsnitt hittar vi några goda skäl till varför en metod skulle vilja referera till sin egen klass.
Varje värde är ett objekt och har därför en klass (även kallad typ). Den lagras som object.__class__
.
9.5. Ärftlighet¶
Naturligtvis skulle en språkfunktion inte vara värd namnet ”klass” utan att stödja arv. Syntaxen för en härledd klassdefinition ser ut så här:
klass AvleddaKlassnamn(BasKlassnamn):
<statement-1>
.
.
.
<statement-N>
Namnet BaseClassName
måste definieras i en namnrymd som är tillgänglig från det scope som innehåller den härledda klassdefinitionen. I stället för ett basklassnamn är även andra godtyckliga uttryck tillåtna. Detta kan vara användbart t.ex. när basklassen är definierad i en annan modul:
class AvleddaKlassnamn(modnamn.BasKlassnamn):
Exekveringen av en härledd klassdefinition sker på samma sätt som för en basklass. När klassobjektet konstrueras kommer basklassen ihåg. Detta används för att lösa attributreferenser: om ett efterfrågat attribut inte finns i klassen, fortsätter sökningen i basklassen. Denna regel tillämpas rekursivt om basklassen i sig själv är härledd från någon annan klass.
Det finns inget speciellt med instansiering av härledda klasser: DerivedClassName()
skapar en ny instans av klassen. Metodreferenser löses på följande sätt: motsvarande klassattribut söks, vid behov nedåt i kedjan av basklasser, och metodreferensen är giltig om detta ger ett funktionsobjekt.
Härledda klasser kan åsidosätta metoder i sina basklasser. Eftersom metoder inte har några speciella privilegier när de anropar andra metoder i samma objekt, kan en metod i en basklass som anropar en annan metod som definieras i samma basklass sluta med att anropa en metod i en härledd klass som åsidosätter den. (För C++-programmerare: alla metoder i Python är i praktiken virtuella
)
En överordnad metod i en härledd klass kan i själva verket vilja utvidga snarare än att bara ersätta basklassens metod med samma namn. Det finns ett enkelt sätt att anropa basklassmetoden direkt: bara anropa BaseClassName.methodname(self, arguments)
. Detta är ibland användbart för klienter också. (Observera att detta endast fungerar om basklassen är tillgänglig som BaseClassName
i det globala omfånget)
Python har två inbyggda funktioner som arbetar med nedärvning:
Använd
isinstance()
för att kontrollera en instants typ:isinstance(obj, int)
kommer att varaTrue
endast omobj.__class__
ärint
eller någon klass härledd frånint
.Använd
issubclass()
för att kontrollera klassens arv:issubclass(bool, int)
ärTrue
eftersombool
är en subklass avint
. Menissubclass(float, int)
ärFalse
eftersomfloat
inte är en subklass avint
.
9.5.1. Multipel nedärvning¶
Python stöder också en form av multipel nedärvning. En klassdefinition med flera basklasser ser ut så här:
klass AvleddaKlassnamn(Bas1, Bas2, Bas3):
<statement-1>
.
.
.
<statement-N>
För de flesta ändamål och i de enklaste fallen kan du tänka dig att sökningen efter attribut som ärvs från en överordnad klass sker med djupet först, från vänster till höger, och inte två gånger i samma klass där det finns en överlappning i hierarkin. Om ett attribut inte hittas i DerivedClassName
, söks det alltså i Base1
, sedan (rekursivt) i basklasserna i Base1
, och om det inte hittas där, söks det i Base2
, och så vidare.
I själva verket är det något mer komplext än så; metodresolutionsordningen ändras dynamiskt för att stödja kooperativa anrop till super()
. Detta tillvägagångssätt är känt i vissa andra språk med flera arv som call-next-method och är kraftfullare än superanropet som finns i språk med enstaka arv.
Dynamisk ordning är nödvändig eftersom alla fall av multipel nedärvning uppvisar en eller flera diamantrelationer (där minst en av de överordnade klasserna kan nås via flera vägar från den underordnade klassen). Till exempel ärver alla klasser från object
, så alla fall av multipel nedärvning ger mer än en väg för att nå object
. För att förhindra att basklasserna anropas mer än en gång linjäriserar den dynamiska algoritmen sökordningen på ett sätt som bevarar den vänster-till-höger-ordning som anges i varje klass, som bara anropar varje överordnad klass en gång och som är monoton (vilket innebär att en klass kan underordnas utan att det påverkar rangordningen för dess överordnade klasser). Sammantaget gör dessa egenskaper det möjligt att utforma tillförlitliga och utbyggbara klasser med multipel arvbarhet. För mer detaljer, se Python 2.3-metodens upplösningsordning.
9.6. Privata variabler¶
”Privata” instansvariabler som inte kan nås annat än från insidan av ett objekt finns inte i Python. Det finns dock en konvention som följs av de flesta Python-koder: ett namn som inleds med ett understreck (t.ex. _spam
) ska behandlas som en icke-publik del av API:et (oavsett om det är en funktion, en metod eller en datamedlem). Det bör betraktas som en implementeringsdetalj och kan ändras utan föregående meddelande.
Eftersom det finns ett giltigt användningsområde för klassprivata medlemmar (nämligen att undvika namnkrockar mellan namn och namn som definieras av underklasser), finns det begränsat stöd för en sådan mekanism, kallad name mangling. Varje 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 inledande understrykning(ar) borttagna. Denna mangling görs utan hänsyn till identifierarens syntaktiska position, så länge den förekommer inom definitionen av en klass.
Se även
Specifikationerna för privat namnmangling för detaljer och specialfall.
Namnmangling är användbart för att låta underklasser åsidosätta metoder utan att bryta metodanrop inom klassen. Till exempel:
klass Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
för objekt i iterabel:
self.items_list.append(item)
__update = update # privat kopia av den ursprungliga update()-metoden
klass MappingSubclass(Mapping):
def update(self, nycklar, värden):
# ger ny signatur för update()
# men bryter inte __init__()
for item in zip(nycklar, värden):
self.items_list.append(item)
Exemplet ovan skulle fungera även om MappingSubclass
skulle införa en __update
identifierare eftersom den ersätts med _Mapping__update
i klassen Mapping
respektive _MappingSubclass__update
i klassen MappingSubclass
.
Observera att mangling-reglerna främst är utformade för att undvika olyckor; det är fortfarande möjligt att komma åt eller ändra en variabel som anses vara privat. Detta kan till och med vara användbart under speciella omständigheter, t.ex. i felsökaren.
Observera att kod som skickas till exec()
eller eval()
inte betraktar namnet på den anropande klassen som den aktuella klassen; detta liknar effekten av global
-satsen, vars effekt också är begränsad till kod som är byte-kompilerad tillsammans. Samma begränsning gäller för getattr()
, setattr()
och delattr()
, samt när man refererar direkt till __dict__
.
9.7. Lite av varje¶
Ibland är det användbart att ha en datatyp som liknar Pascals ”record” eller C:s ”struct”, som samlar ihop några namngivna dataobjekt. Det idiomatiska tillvägagångssättet är att använda dataclasses
för detta ändamål:
from dataclasses import dataclass
@dataklass
klass Anställd:
namn: str
avdelning: str
lön: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000
En del av Python-koden som förväntar sig en viss abstrakt datatyp kan ofta skickas till en klass som emulerar metoderna för den datatypen istället. Om du till exempel har en funktion som formaterar data från ett filobjekt kan du definiera en klass med metoderna read()
och readline()
som hämtar data från en strängbuffert istället, och skicka den som argument.
Instansmetodobjekt har också attribut: m.__self__
är instansobjektet med metoden m()
, och m.__func__
är funktionsobjektet som motsvarar metoden.
9.8. Iteratorer¶
Vid det här laget har du säkert märkt att de flesta containerobjekt kan loopas över med hjälp av en for
-sats:
för element i [1, 2, 3]:
print(element)
för element i (1, 2, 3):
print(element)
for key in {'ett':1, 'två':2}:
print(nyckel)
för char i "123":
print(char)
for line in open("minfil.txt"):
print(rad, end='')
Denna typ av åtkomst är tydlig, kortfattad och bekväm. Användningen av iteratorer genomsyrar och förenar Python. Bakom kulisserna anropar for
-satsen iter()
på containerobjektet. Funktionen returnerar ett iteratorobjekt som definierar metoden __next__()
som öppnar elementen i containern ett i taget. När det inte finns några fler element ger __next__()
upphov till ett StopIteration
-undantag som säger till for
-slingan att avslutas. Du kan anropa metoden __next__()
med hjälp av den inbyggda funktionen next()
; detta exempel visar hur det fungerar:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
Efter att ha sett mekaniken bakom iteratorprotokollet är det enkelt att lägga till iteratorbeteende i dina klasser. Definiera en __iter__()
-metod som returnerar ett objekt med en __next__()
-metod. Om klassen definierar __next__()
, så kan __iter__()
bara returnera self
:
klass Omvänd:
"""Iterator för att loopa över en sekvens baklänges."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
9.9. Generatorer¶
Generators är ett enkelt och kraftfullt verktyg för att skapa iteratorer. De skrivs som vanliga funktioner men använder yield
-satsen när de vill returnera data. Varje gång next()
anropas fortsätter generatorn där den slutade (den kommer ihåg alla datavärden och vilket uttalande som senast kördes). Ett exempel visar att generatorer kan vara trivialt enkla att skapa:
def reverse(data):
för index i intervall(len(data)-1, -1, -1):
avkastning data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
Allt som kan göras med generatorer kan också göras med klassbaserade iteratorer enligt beskrivningen i föregående avsnitt. Det som gör generatorer så kompakta är att metoderna __iter__()
och __next__()
skapas automatiskt.
En annan viktig egenskap är att de lokala variablerna och exekveringstillståndet sparas automatiskt mellan anropen. Detta gjorde funktionen enklare att skriva och mycket tydligare än ett tillvägagångssätt som använder instansvariabler som self.index
och self.data
.
Förutom automatiskt skapande av metoder och sparande av programstatus ger generatorer automatiskt upphov till StopIteration
när de avslutas. I kombination gör dessa funktioner det enkelt att skapa iteratorer utan större ansträngning än att skriva en vanlig funktion.
9.10. Generatoruttryck¶
Vissa enkla generatorer kan kodas kortfattat som uttryck med en syntax som liknar listförståelser men med parenteser istället för hakparenteser. Dessa uttryck är utformade för situationer där generatorn används direkt av en omslutande funktion. Generatoruttryck är mer kompakta men mindre mångsidiga än fullständiga generatordefinitioner och tenderar att vara mer minnesvänliga än motsvarande listkomprehensioner.
Exempel:
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
Fotnoter