Start | dundermetoder
 

Klasser: dundermetoder



Pythons magiska dundermetoder tillhandahåller ett enkelt sätt att få egna skapade objekt att bete sig som de inbyggda objekten i python.

bidrag till polymorfismen

Så, med dundermetoder kan vi kan få våra egna objekt att fungera med, inte bara inbyggda infixa operatorer för t.ex. räknesätt (+ - * / % & |) och jämförelser utan också andra vanliga metoder som används, t.ex. print(), abs(), osv.

Det finns alltså ett gäng fördefinierade metoder med vars hjälp vi kan koppla inbyggda funktioner i python till våra klasser. Om du kör nedan lilla kod, så får du en glimt vilka dundermetoderna är.
print(dir(int))

Exempel: komplex





Enklaste sättet få kunskapen att flyga är nog med ett exempel. Låt säga vi skapar en klass för komplexa tal. Typ, såhär:
class komplex: def __init__(self,r,i): self.r, self.i = r, i c1 = komplex(1,1) c2 = komplex(1,1)
Säg att vi vill skapa en utskriftsfunktion så att vi kan titta på innehållet i vår klass. Vi kan t.ex. skapa en metod vi kallar print_komplex().
class komplex: def __init__(self,r,i): self.r, self.i = r, i def print_komplex(self): print("{:+f} {:+f}i".format(self.r, self.i)) c1 = komplex(1,1) c2 = komplex(1,1) c1.print_komplex() c2.print_komplex()
Den gör jobbet, men hade det inte varit snyggare om vi kunde använda print() ? Vi kan ju använda print till alla möjliga datatyper utan att behöva tänka så mycket, varför inte också vårt komplexa tal?

Det finns en dunder -metod som heter __str__ vilken länkar upp möjligheten göra utskrifter till printfunktionen. Det som gäller är att __str__ skall lämna ifrån sig en sträng. Så vi kan komponera en sträng...

__str__


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) c1 = komplex(1,1) c2 = komplex(1,1) print(c1) print(c2)
Vidare, vore det inte otroligt snyggt om vi även kunde addera 2 stycken komplexa tal med + -operatorn? Det finns en dunder -metod även för detta, nämligen __add__

__add__


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) def __add__(self,other): return(komplex(self.r+other.r,self.i+other.i)) c1 = komplex(1,1) c2 = komplex(1,1) print(c1+c2)
Notera ovan att, givet att du definierar metoden __add__, faktiskt kan använda den infixa operatorn + mellan dina egna objekt. Infix betyder att argumenten finns till höger och vänster om metoden. Låt inte detta ögonblick av insikt passera oreflekterat. När vi ändå är på gång, kanske vi kan lägga till subtraktion också. Dundermetoden heter __sub__

__sub__


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) def __add__(self,other): return(komplex(self.r+other.r,self.i+other.i)) def __sub__(self,other): return(komplex(self.r-other.r,self.i-other.i)) c1 = komplex(1,1) c2 = komplex(1,1) print(c1+c2) print(c1-c2-c1)
Vi vill även kunna multiplicera 2 stycken komplexa tal. Dundermetoden som * -operatorn heter __mul__

Du börjar fatta konceptet?

__mul__


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) def __add__(self,other): return(komplex(self.r+other.r,self.i+other.i)) def __sub__(self,other): return(komplex(self.r-other.r,self.i-other.i)) def __mul__(self,other): return(komplex( self.r*other.r-self.i*other.i, self.r*other.i+self.i*other.r)) c1 = komplex(3,2) c2 = komplex(4,1) print(c1*c2)


Några dundermetoder för infixa operatorer ...

__add__Infixa operatorn +
__sub__Infixa operatorn -
__mul__Infixa operatorn *
__div__Infixa operatorn /
__mod__Infixa operatorn %

Addition, subtraktion, multiplikation m.fl. är infixa operatorer. Det är väldigt snyggt att dessa går att använda. Men det går även att länka andra inbygga icke infixa vanliga funktioner, som t.ex. abs().

Absolutbeloppet, när vi pratar komplexa tal, det är ju som bekant hypotenusan i triangeln där sidorna är realdelen och imaginärdelen. Så vi ger abs(komplex) ett liv genom dundermetoden __abs__

__abs__ = abs()


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) def __add__(self,other): return(komplex(self.r+other.r,self.i+other.i)) def __sub__(self,other): return(komplex(self.r-other.r,self.i-other.i)) def __mul__(self,other): return(komplex( self.r*other.r-self.i*other.i, self.r*other.i+self.i*other.r)) def __abs__(self): return((self.r**2+self.i**2)**0.5) c1 = komplex(3,2) c2 = komplex(4,1) print(abs(c1)) print(abs(c1*c2))
Förutom dundermetoder för infixa operatorer (räknesätten m.fl.) och vissa vanliga funktioner (abs(), print() t.ex.), så finns även dundermetoder för unära operatorer. Exempel på en sådan är ~ (tilde) som används för bitvis invertering. Låt oss använda denna __invert__ för att skapa metoden invers.

__invert__ = ~


class komplex: def __init__(self,r,i): self.r, self.i = r, i def __str__(self): return("{:+f} {:+f}i".format(self.r, self.i)) def __add__(self,other): return(komplex(self.r+other.r,self.i+other.i)) def __sub__(self,other): return(komplex(self.r-other.r,self.i-other.i)) def __mul__(self,other): return(komplex( self.r*other.r-self.i*other.i, self.r*other.i+self.i*other.r)) def __invert__(self): konj = komplex(self.r,-self.i) namn = self.r**2+self.i**2 return(komplex(konj.r/namn, konj.i/namn)) def __abs__(self): return((self.r**2+self.i**2)**0.5) c1 = komplex(3,2) c2 = komplex(4,1) print(~c1) print(~c2)
Sådär kan vi hålla på. Det finns ganska många dunderfunktioner (se listan nedan).

rmul, radd, rsub, osv.

Om du sneglar på listan av dundermetoder nedan, så upptäcker du att det finns 2 eller 3 versioner av samma dundermetod ibland, där de andra varianterna har ett litet "r" framför sig. R står för right och L står för left om det förekommer. T.ex. av __mul__ (multiplikation) finns även varianten __rmul__ (right -multiplikation).

Låt oss titta lite på hur detta fungerar.

X * Y kommer anropa X.__mul__(Y) om __mul__ existerar i X. Vilken datatyp Y skall ha, det kan du bestämma själv. Det är ju du som skriver __mul__ metoden! Men låt säga det är tvärtom. Du har X * Y men du vill att operationen ska genomföras som Y.__mul__(X) därför att det är argumentet X som är lite speciellt, då måste du använda __rmul__

Exempel: Säg att du skapar en klass för matriser och vill implementera skalär, dvs att du kan multiplicera alla element i matrisen med en viss konstant. Du utför multiplikationen som t.ex. 3 * M, där 3 är en siffra och M är din matris. Då uppkommer direkt situationen att talet du ska multiplicera matrisen med är int eller float medans matrisen är ditt Matris -objekt. Din int eller float har ingen __mul__ -metod i sig som förstår din Matris -klass. Det är först efter X som din matris dyker upp, dvs du har situationen där du vill utföra Y.__mul__(X). Lösning är att sätta multiplikationen under dundermetoden __rmul__. Dvs, python använder dunderfunktionen i klassen Matris. Prova ta bort "r" i __rmul__ och kör koden. Vad får får du för fel? Du får detta fel:

TypeError: unsupported operand type(s) for *: 'int' and 'Matrix'

class Matrix: def __init__(self, I=None): if(I!=None): self.I = I self.w = len(self.I[0]) self.h = len(self.I) def __str__(self): s = str(self.h)+'x'+str(self.w)+"\n" for i in range(self.h): for j in range(self.w): s = s + "{:>4}".format(self.I[i][j]) s = s + "\n" return(s) def __rmul__(self,K): C = [[0]*self.w for i in range(self.h)] for i in range(self.w): for j in range(self.h): C[j][i]= self.I[j][i] * K return(Matrix(C)) T = Matrix( [[1,2], [3,4], [5,1]]) U = Matrix( [[3,4], [4,5], [2,2]]) print(T) print("Skala upp med 2") print(2 * T) print(U) print("Skala upp med 3") print(3 * U)

dundermetoder


Nedan är inte alla metoder, men de flesta.

Matematik


AnvändningUtförsNotis/Returvärde
x + y x.__add__(y)
x + y y.__radd__(x)
x - y x.__sub__(y)
x - y y.__rsub__(x)
x * y x.__mul__(y)
x * y y.__rmul__(x)
x / y x.__truediv__(y)
x / y y.__rtruediv__(x)
x % y x.__mod__(y)
x % y y.__rmod__(x)
x // y x.__floordiv__(y)
x // y y.__rfloordiv__(x)
x ** y x.__pow__(y)
x ** y y.__rpow__(x)

Bitvisa operationer


AnvändningUtförsNotis/Returvärde
x @ y x.__matmul__(y)Matrismult.
x @ y y.__rmatmul__(x)Matrismult.
x & y x.__and__(y)Bitvis and
x & y y.__rand__(x)Bitvis and
x | y x.__or__(y)Bitvis eller
x | y y.__ror__(x)Bitvis eller
x ^ y x.__xor__(y)Bitvis xor
x ^ y y.__rxor__(x)Bitvis xor
x >> y x.__rshift__(y)Bitvis shift
x >> y y.__rrshift__(x)Bitvis shift
x << y x.__lshift__(y)Bitvis shift
x << y y.__rlshift__(x)Bitvis shift
-x x.__neg__()
+x x.__pos__()
~x x.__invert__()
x &= y x.__iand__(y)
x |= y x.__ior__(y)
x ^= y x.__ixor__(y)
x >>= y x.__irshift__(y)
x <<= y x.__ilshift__(y)

Matematiska funktioner


AnvändningUtförsNotis/Returvärde
divmod(x, y) x.__divmod__(y) Tuple (t,n)
abs(x) x.__abs__() float
indexx.__index__() int
round(x) x.__round__() Number
math.trunc(x) x.__trunc__() Number
math.floor(x) x.__floor__() Number
math.ceil(x) x.__ceil__() Number

Kortare version av inkrementeringar


AnvändningUtförsNotis/Returvärde
x += y x.__iadd__(y)
x -= y x.__isub__(y)
x *= y x.__imul__(y)
x /= y x.__itruediv__(y)
x %= y x.__imod__(y)
x //= y x.__ifloordiv__(y)
x **= y x.__ipow__(y)
x @= y x.__imatmul__(y)

Jämförelseoperationer


AnvändningUtförsNotis/Returvärde
x == y x.__eq__(y) bool
x != y x.__ne__(y) bool
x < y x.__lt__(y) bool
x > y x.__rt__(y) bool
x <= y x.__le__(y) bool
x >= y x.__ge__(y) bool

Konverteringar


AnvändningUtförsNotis/Returvärde
repr(x) x.__repr__() str
str(x) x.__str__() str
bool(x) x.__bool__() bool
int(x) x.__int__() int
float(x) x.__float__() float
bytes(x) x.__bytes__() bytes
complex(x) x.__complex__() complex
format(x, s) x.__format__(s) str

Kontextoperatorer


AnvändningUtförsNotis/Returvärde
with x as obj: x.__enter__() obj objektet
with x as obj: x.__exit__() True/False

Iteratorer och listor


AnvändningUtförsNotis/Returvärde
iter(x) x.__iter__() Iterator
reversed(x) x.__reversed__() Rev. iterator
next(x) x.__next__() Nästa iterator item
len(x) x.__len__() int
x[a] x.__getitem__(a)
x[a] = b x.__setitem__(a, b)
del x[a] x.__delitem__(a)
a in x x.__contains__(a) bool
x[a] x.__missing__(a)
x.__length_hint__() int

Övrigt


AnvändningUtförsNotis/Returvärde
x.y x.__getattribute__('y')
x.y x.__getattr__('y')
x.y = z x.__setattr__('y', z)
del x.y x.__delattr__('y')
dir(x) x.__dir__() iterable
t.x T.x.__get__(t, T)
t.x = y T.x.__set__(t, y)
del t.x T.x.__delete__(t)
x = T(a, b) x.__init__(a, b)
x = T(a, b) T.__new__(T, a, b) Ny instans (x)
hash(x) x.__hash__() int
del x (ish) x.__del__()
await x (ish) x.__await__() iterator
async with x: x.__aenter__() awaitable
async with x: x.__aexit__() awaitable
async for a in x: x.__aiter__() awaitable
async for a in x: x.__anext__() awaitable


lite frågor ...

Vad är en dundermetod?
  Ett enkelt sätt att fixa dunder och brak i den egna klassen
  Ett sätt att länka upp inbyggda vanliga operatorer och funktioner till den egna klassen
Vad gör __mul__ ?
  Vid X * Y anropas Y.__mul__(X), dvs med X som argument.
  Vid X * Y anropas X.__mul__(Y), dvs med Y som argument.
Vad gör __rmul__ ?
  Vid X * Y anropas Y.__rmul__(X), dvs med X som argument.
  Vid X * Y anropas X.__rmul__(Y), dvs med Y som argument.
När vill du använda __rmul__ ?
  Om vi har X * Y och det behövs att det är Y som gör __rmul__, därför att X är av en annan typ.
  Om vi har X * Y och det behövs att det är X som gör __rmul__, därför att Y är av en annan typ.
17.516136169434 ms