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 = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
Utmatningen av exempelkoden är:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
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. Syntax för klassdefinition¶
Den enklaste formen av klassdefinition ser ut så här:
class ClassName:
<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:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
then MyClass.i and MyClass.f are valid attribute references, returning
an integer and a function object, respectively. Class attributes can also be
assigned to, so you can change the value of MyClass.i by assignment.
__doc__ is also a valid attribute, returning the docstring belonging to
the class: "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.
data attributes correspond to ”instance variables” in Smalltalk, and to ”data
members” in C++. Data attributes need not be declared; like local variables,
they spring into existence when they are first assigned to. For example, if
x is the instance of MyClass created above, the following piece of
code will print the value 16, without leaving a trace:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
The other kind of instance attribute reference is a method. A method is a function that ”belongs to” an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)
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()
In the MyClass example, this will return the string 'hello world'.
However, it is not necessary to call a method right away: x.f is a method
object, and can be stored away and called at a later time. For example:
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'
As discussed in Några ord om namn och objekt, shared data can have possibly surprising effects with involving mutable objects such as lists and dictionaries. For example, the tricks list in the following code should not be used as a class variable because just a single list would be shared by all Dog instances:
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:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
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:
class Bag:
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:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
The name BaseClassName must be defined in a scope containing the
derived class definition. In place of a base class name, other arbitrary
expressions are also allowed. This can be useful, for example, when the base
class is defined in another module:
class DerivedClassName(modname.BaseClassName):
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 virtual)
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 varaTrueendast omobj.__class__ärinteller någon klass härledd frånint.Använd
issubclass()för att kontrollera klassens arv:issubclass(bool, int)ärTrueeftersomboolär en subklass avint. Menissubclass(float, int)ärFalseeftersomfloatinte ä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:
class DerivedClassName(Base1, Base2, Base3):
<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.
Dynamic ordering is necessary because all cases of multiple inheritance exhibit
one or more diamond relationships (where at least one of the parent classes
can be accessed through multiple paths from the bottommost class). For example,
all classes inherit from object, so any case of multiple inheritance
provides more than one path to reach object. To keep the base classes
from being accessed more than once, the dynamic algorithm linearizes the search
order in a way that preserves the left-to-right ordering specified in each
class, that calls each parent only once, and that is monotonic (meaning that a
class can be subclassed without affecting the precedence order of its parents).
Taken together, these properties make it possible to design reliable and
extensible classes with multiple inheritance. For more detail, see
https://www.python.org/download/releases/2.3/mro/.
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.
Namnmangling är användbart för att låta underklasser åsidosätta metoder utan att bryta metodanrop inom klassen. Till exempel:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
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
@dataclass
class Employee:
name: str
dept: str
salary: 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:
for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line, 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:
class Reverse:
"""Iterator for looping over a sequence backwards."""
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):
for index in range(len(data)-1, -1, -1):
yield 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