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
Attributes may be read-only or writable. In the latter case, assignment to
attributes is possible. Module attributes are writable: you can write
modname.the_answer = 42. Writable attributes may also be deleted with the
del statement. For example, del modname.the_answer will remove
the attribute the_answer from the object named by 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.
When a class definition is left normally (via the end), a class object is
created. This is basically a wrapper around the contents of the namespace
created by the class definition; we’ll learn more about class objects in the
next section. The original local scope (the one in effect just before the class
definition was entered) is reinstated, and the class object is bound here to the
class name given in the class definition header (ClassName in the
example).
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.
The instantiation operation (”calling” a class object) creates an empty object.
Many classes like to create objects with instances customized to a specific
initial state. Therefore a class may define a special method named
__init__(), like this:
def __init__(self):
self.data = []
When a class defines an __init__() method, class instantiation
automatically invokes __init__() for the newly created class instance. So
in this example, a new, initialized instance can be obtained by:
x = MyClass()
Of course, the __init__() method may have arguments for greater
flexibility. In that case, arguments given to the class instantiation operator
are passed on to __init__(). For example,
>>> 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.
What exactly happens when a method is called? You may have noticed that
x.f() was called without an argument above, even though the function
definition for f() specified an argument. What happened to the argument?
Surely Python raises an exception when a function that requires an argument is
called without any — even if the argument isn’t actually used…
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.
If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.
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
Now f, g and h are all attributes of class C that refer to
function objects, and consequently they are all methods of instances of
C — h being exactly equivalent to g. Note that this practice
usually only serves to confuse the reader of a 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>
For most purposes, in the simplest cases, you can think of the search for
attributes inherited from a parent class as depth-first, left-to-right, not
searching twice in the same class where there is an overlap in the hierarchy.
Thus, if an attribute is not found in DerivedClassName, it is searched
for in Base1, then (recursively) in the base classes of Base1,
and if it was not found there, it was searched for in Base2, and so on.
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
A piece of Python code that expects a particular abstract data type can often be
passed a class that emulates the methods of that data type instead. For
instance, if you have a function that formats some data from a file object, you
can define a class with methods read() and readline() that get the
data from a string buffer instead, and pass it as an argument.
Instance method objects have attributes, too: m.__self__ is the instance
object with the method m(), and m.__func__ is the function object
corresponding to the method.
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
Having seen the mechanics behind the iterator protocol, it is easy to add
iterator behavior to your classes. Define an __iter__() method which
returns an object with a __next__() method. If the class
defines __next__(), then __iter__() can just return 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
Anything that can be done with generators can also be done with class-based
iterators as described in the previous section. What makes generators so
compact is that the __iter__() and __next__() methods
are created automatically.
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
- 1
Except for one thing. Module objects have a secret read-only attribute called
__dict__which returns the dictionary used to implement the module’s namespace; the name__dict__is an attribute but not a global name. Obviously, using this violates the abstraction of namespace implementation, and should be restricted to things like post-mortem debuggers.