Vetenskapligt Python: Vad är magiska metoder?

Den här artikeln belyser de specialmetoder i Python som alla Pythonprogrammerare måste känna till

Farhad Malik

Follow

16 maj, 2020 – 14 min read

De magiska metoderna hjälper oss att berika våra applikationer. De tillför indirekt magi till vår Pythonkod. Detta är ett ämne på avancerad nivå för Python-utvecklare och jag rekommenderar det till alla som använder eller tänker använda programmeringsspråket Python.

De magiska metoderna ger oss mer kontroll över hur vår applikation beter sig.

Denna artikel syftar till att förklara vad magiska metoder är och hur de kan användas för att bygga Python-applikationer. Den kommer att ge en översikt över de mest använda magiska metoderna för en rad datatyper.

Syftet med att beskriva de viktigaste magiska metoderna är att vi ska förstå om vi vill åsidosätta dessa metoder i våra anpassade klasser för att berika applikationerna.

Magiska metoder gör programmeringsspråket Python extremt kraftfullt

Foto av Rodion Kutsaev på Unsplash

Vad är Pythons magiska metoder?

Python magiska metoder är också kända som specialmetoder eller dundermetoder. De omges av dubbla understrykningar t.ex. __init__().

Ett objekt kan ha ett antal magiska metoder.

Håll dig till att allt i Python är ett objekt inklusive en variabel/funktion/klass osv. Objekten är Pythons abstraktion för data.

De magiska metoderna används för att konstruera och initiera nya objekt, de hjälper oss att hämta ett objekt som en ordbok, de används för att ta bort ett objekt bland andra operationer. De används när operatören + åberopas, eller till och med när vi vill representera ett objekt som en sträng.

Tyvärr är varje metod i Python offentlig, men kodningskonventionen är att omge alla privata metoder med dubbla understrykningar __<method>__()

Detta innebär att de magiska metoderna är avsedda att vara privata metoder. Det betyder också att den som anropar ett objekt inte ska anropa metoden direkt eftersom metoden är avsedd att anropas internt av den klass som har den magiska metoden.

Vi kan åsidosätta de magiska metoderna för att tillhandahålla vår egen anpassade funktionalitet.

Foto av Cristian Escobar på Unsplash

Jag kommer att förklara begreppen magiska metoder genom att skapa en anpassad klass och sedan kommer jag att ge en översikt över de viktigaste magiska metoderna i ett heltal, en sträng, en lista och en ordbok.

När vi går vidare i artikeln kommer den att börja bygga upp en mycket tydligare bild av varför de magiska metoderna finns och hur man använder dem.

Om du vill förstå programmeringsspråket Python från nybörjare till avancerad nivå så rekommenderar jag starkt artikeln nedan:

Jag kommer att börja ämnet magiska metoder genom att skapa en egen klass och sedan kommer jag att förklara hur de magiska metoderna används.

Användningsfallet för att förstå magiska metoder

I utdragsrutan nedan har jag skapat en klass som heter Human och sedan instansierade jag en instans av klassen Human.

Detta kodutdrag kommer att användas för att hjälpa oss att förstå magiska metoder.

Notera att id för objektet human är av typen heltal, attributet name är av typen sträng, egenskapen addresses är av typen lista och egenskapen maps är av typen dictionary.

Jag har grupperat de magiska metoderna i olika datatypavsnitt för att underlätta läsningen. Samma magiska metoder finns dock i olika datatyper.

De magiska metoderna kan åsidosättas för att berika funktionaliteten och skapa anpassad logik som bäst passar verksamhetens behov.

Klass:

Låt oss förstå de magiska metoder som är kopplade till det mänskliga objektet. Om jag utför metoden dir(human) kommer den att lista alla funktioner och attributnamn för det mänskliga objektet.

Det finns totalt 29 metoder/attribut som är associerade med det mänskliga objektet. Av dem är 26 magiska metoder.

Det är ett ganska stort antal specialmetoder. Dessa metoder ärvs från människoklassens bastyp. Därför är de fördefinierade metoder som vi kan använda/överrida för att berika klasserna.

Nästa del av avsnittet kommer att förklara de viktigaste magiska metoderna.

__delattr__

Denna metod anropas när vi försöker ta bort ett attribut från en klass.

Vi kan åsidosätta funktionaliteten genom att implementera metoden i klassen Human:

def __delattr__(self, item):
print('Deleting attribute ' + item)
return object.__delattr__(self, item)

Nu, när vi försöker ta bort ett attribut, visas meddelandet: Delete attribute

Det finns också metoden __setattr__() för att tilldela ett värde till ett attribut och __getattr__() för att hämta ett värde från attributet.

Ett användningsområde för__delattr__() kan vara att förhindra att vissa attribut i ett objekt tas bort eller när vi vill utföra specifika åtgärder när ett visst attribut tas bort.

__dict__

Denna metod returnerar ett lexikon som representerar objektet. Nycklarna i ordlistan är objektets attribut och värdena är attributens värden.

Som instans:

human = Human(2, 'Malik').__dict__
print(human)

Koden ovan returnerar:

{’id’: 2, ’name’: ’Malik’, ’addresses’: , ’maps’: {}}

__dir__

Vi kan åsidosätta dir()-metoden genom att åsidosätta __dir__()-metoden i klassen. Som exempel kan vi ta bort de interna metoderna från resultatet som returneras av dir()-metoden:

__eq__

Denna metod anropas när vi försöker utföra ==-operationen. Låt oss tänka oss att två mänskliga objekt är lika när deras id-attribut är lika även om deras namn är olika.

Vi kan åsidosätta metoden __eq__() för att uppnå denna funktionalitet:

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

Detta kommer nu att återge True:

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
print(first == second)

__format__

Varhelst vi försöker göra string.format() anropas metoden __format__() internt.

__ge__

Som exempel kan vi anta att det finns två Human-objekt:

first = Human(1, 'Farhad')
second = Human(2, 'Malik')

Vi kan också tänka oss att vårt projekt har en regel som säger att ett mänskligt objekt med ett större Id anses vara större än det andra mänskliga objektet. Därför kan vi åsidosätta metoden __gt__() och implementera den anpassade logiken:

def __ge__(self, other):
return self.id >= other.id

Detta kommer nu att återge False eftersom id för det andra mänskliga objektet är större än det första mänskliga objektet:

print(first >= second)
Returns False

Det finns också en metod __lt__() som exekveras när operatören ≤ utförs.

__hash__

Hashing används för att konvertera ett objekt till ett heltal. Hashning utförs när vi försöker ställa in ett objekt i ett lexikon/set.

En bra hashningsalgoritm resulterar i ett lägre antal hashningskollisioner. Vi kan tillhandahålla vår egen hash-algoritm genom att överskriva metoden __hash__().

Vi kan tänka oss att id:et för Human-objektet ska vara unikt i vårt program. Algoritmen __hash__() kan åsidosättas för att returnera self.id som hash heltal:

def __hash__(self):
return self.id

Vi kan skapa två objekt och spara dem i en set collection. När vi frågar efter längden på uppsättningen kommer vi att förvänta oss två element i uppsättningen eftersom båda objekten har olika id.

first = Human(1, 'Farhad')
second = Human(2, 'Malik')
my_set = set()
print(len(my_set))
#returns 2

Om vi nu ställer in id till 1 för båda objekten och upprepar övningen kommer vi bara att se 1 element i uppsättningen eftersom båda objekten har samma hash-nyckel eftersom deras id-attribut är detsamma, även om deras namnattribut är olika.

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
my_set = set()
print(len(my_set))
#returns 1

__init__

Metoden __init__() utförs när vi vill instabilisera en ny instans av en klass genom att kalla dess konstruktör.

Som en instans, när vi försökte exekvera:

human = Human(1, 'farhad')

Då exekverades metoden __init__().

Vi kan åsidosätta funktionaliteten och skicka in våra egna anpassade argument och beteenden i den också.

Som exempel är __init__()-metoden för klassen Human:

def __init__(self, id, name, addresses=, maps={}):
self.id = id
self.name = name
self.addresses = addresses
self.maps = maps

Foto av Cederic X på Unsplash

__init_subclass__

Detta är ett av metaklassens användningsområden. När en klass underklassas och dess objekt skapas så anropas metoden __init_subclass__().

Metoden informerar i princip föräldern om att den har blivit underklassad. Denna hook kan sedan initialisera alla underklasser av en viss klass. Metoden används därför för att registrera underklasser och tilldela standardvärden till attribut i underklasserna. Den gör det alltså möjligt att anpassa initialiseringen av underklasser.

Jag kommer att förklara hur metaklasser fungerar i min nästa artikel.

__new__

När vi vill instansiera/skapa en ny instans av en klass så körs metoden __new__(cls).

Som exempel kan vi tänka oss att vi vill skriva ut ”Human creating…” varje gång konstruktören Human() anropas.

Vi kan åsidosätta funktionaliteten hos metoden __new__(cls) enligt nedan:

def __new__(cls, *args, **kwargs):
print('Human creating...')
return object.__new__(cls)

Som ett resultat skrivs Human creating… ut när vi försökte skapa en instans:

human = Human(1, 'Farhad', , {'London':2, 'UK':3})

__sizeof__

Denna metod anropas när vi utför sys.getsizeof(). Den returnerar storleken på objektet i minnet, i byte.

__str__

Det skrevs ut: id=1. name=Farhad.

Funktionen __str__() bör försöka returnera en användarvänlig representation av objektet.

__weakref__

Detta __weakref__-objekt returnerar listan över svaga referenser till målobjektet. I huvudsak hjälper det sophämtningen att informera svaga referenser om att referenten har samlats in. Därför förhindrar det att objekten får tillgång till de underliggande pekarna.

Foto av Yousef Espanioly på Unsplash

Integer

Det här för oss vidare till nästa avsnitt i artikeln. Vi kan se att id-egenskapen för objektet human är av typen int. Om vi sedan utför dir() på id-egenskapen och filtrerar bort de metoder som är omgivna av dubbla understrykningar kommer vi att stöta på att det finns totalt 62 magiska metoder.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.id)))))

Jag kommer att förklara de viktigaste metoderna här:

__add__

Denna metod anropas när vi försöker addera två tal.

Som exempel:

human.id + 2 är detsamma som human.id.__add__(2)

__and__

Den här metoden exekveras när vi försöker använda &operatören e.g.:

return self & another_value

__bool__

Denna metod exekveras när vi försöker utföra den boolska kontrollen på ett objekt e.g.

self != 123

__floordiv__

Denna metod exekveras när vi utför //operatorn.

Foto av Johannes Plenio på Unsplash

__getnewargs__

Från och till plockar vi objekt i Python. Pickling skapar en byte-stream-representation av ett objekt i minnet som vid behov kan sparas på disk.

Metoden __getnewargs__() informerar picklingprocessen om hur den behöver ladda tillbaka det picklade objektet för att återskapa målobjektet. I synnerhet hur objektet måste skapas genom att skicka in argumenten till new()-metoden. Därav namnet ”get new args”.

__index__

Följaktligen kan ett objekt omvandlas till ett heltal genom att utföra metoden __index__(). Vi kan också åsidosätta metoden och tillhandahålla vår egen funktionalitet för hur indexet måste genereras.

__invert__

Denna metod utförs när vi använder ~-operatören.

Som instans:

first = Human(1, 'Farhad')
print(~first.id)

Det är samma sak som att exekvera:

first = Human(1, 'Farhad')
print(first.id.__invert__())

__lshift__

Denna metod ger oss en vänsterförskjutning av ett värde t.ex. self << värde. Vi kan överbelasta <<-operatören genom att överskriva metoden __lshift__().

Notera: __rshift__() exekveras när vi utför >>-operatören.

__mod__

Den här metoden anropas när vi använder operatören %.

__neg__

Denna metod utförs när vi använder den negativa – operatören t.ex.

first.id — second.id

__subclasshook__

Denna metod kan åsidosättas för att anpassa metoden issubclass(). I huvudsak returnerar den True om en klass är en underklass och False om den inte är det. Metoden returnerar också NotImplemented vilket gör att den befintliga algoritmen kan användas.

Metoden kan anpassa resultatet av metoden issubclass().

Foto av Patrick Selin på Unsplash

String

Det här för oss till nästa avsnitt i artikeln.

Vi kan se att egenskapen name för objektet human är av typen string. Om vi sedan utför dir(human.name) på egenskapen och filtrerar bort de metoder som är omgivna av dubbla understrykningar kommer vi att märka att det finns totalt 33 magiska metoder.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.name)))))

Jag kommer att förklara de 4 viktigaste metoderna här eftersom resten av artikeln redan har belyst de flesta magiska metoder.

__contains__

Denna metod utförs när vi försöker kontrollera om ett givet tecken existerar.

__len__

Denna metod returnerar längden på strängen. Den utförs när vi utför len()-metoden.

Om vi vill räkna endast specifika tecken för att beräkna strängens längd kan metoden __()__ åsidosättas för att tillhandahålla den funktionen.

__repr__

Denna metod utförs när vi vill skapa en utvecklarvänlig representation av ett objekt.

Notera att __repr__ exekveras när vi print(object) om vi inte har en __str__()-implementation i vår klass.

def __repr__(self):
return f'id={self.id} ({type(self.id)}). name={self.name} ({type(self.name)})'print(Human(1, 'Farhad'))

Detta kommer att skriva ut:

id=1 (<klass ’int’>). name=Farhad (<klass ’str’>)

Metoden __repr__() bör vara avsedd att producera den officiella representationen av ett objekt.

__iadd__

Från och till använder vi en additionsoperator tillsammans med tilldelningen e.g.

self.id += 1

Detta motsvarar self.id = self.id + 1

Metoden iadd() exekveras när vi utför additionen med tilldelningen.

För övrigt exekveras metoden __ipow__() när **= utförs.

Den magiska metoden __iand__() används för att utföra bitvis AND med tilldelningar och __ior__() anropas när vi försöker göra !=, t.ex. i != j

List

Detta för oss till nästa avsnitt i artikeln. Egenskapen addresses för objektet human är av typen list. Om vi sedan utför dir(human.addresses) på egenskapen addresses och filtrerar bort de metoder som är omgivna av dubbla understrykningar kommer vi att stöta på att det finns totalt 35 magiska metoder.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.addresses)))))

Jag kommer att förklara de viktigaste metoderna här:

__reduce__

När ett objekt picklas utförs metoden __reduce__() för att returnera ett objekt som hjälper picklaren att förstå hur den ska konstruera tillbaka det.

__reduce_ex__

Metoden __reduce_ex__() föredras av pickle framför metoden __reduce__().

Metoden __reduce_ex__() tar ett heltalsargument som är protokollversionen. Den ger bakåtkompatibilitet för pickle och används för att konstruera den picklade byte-strömmen till objektet.

__reversed__

Metoden __reversed__() exekveras när vi försöker vända en samling i omvänd ordning.

Den exekveras när reversed(collection) eller collection.reverse() kallas. Ibland bestämmer vi oss för att ändra funktionaliteten hos metoden reversed().

Genom att överskriva metoden __reversed__() kan vi uppnå det önskade resultatet.

Dictionary

Detta för oss till artikelns femte avsnitt.

Dictionary är en av de viktigaste inbyggda typerna i Python. Om vi utför dir(human.maps) och filtrerar bort de metoder som är omgivna av dubbla understreckare kommer vi att stöta på att det finns totalt 29 magiska metoder.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.maps)))))

Av de 29 magiska metoderna kommer jag att förklara de 4 viktigaste metoderna här:

__delitem__

Den här metoden exekveras när vi tar bort ett objekt e.g.

del dictionary

__getitem__

Denna metod utförs när vi försöker hämta ett objekt för en nyckel:

item = dictionary

__setitem__

Denna metod utförs när vi försöker ställa in ett objekt i ordlistan:

dictionary = item

__iter__

Denna metod returnerar en iterator för samlingen. En iterator hjälper oss att iterera över en samling.

Vi kan åsidosätta hur iterator() exekveras genom att åsidosätta metoden __iter__().

Sist en anmärkning om __call__()

Hur skulle det vara om vi ville göra vårt objekt anropbart? Låt oss tänka oss att vi vill göra objektet Human till en anropsbar human()-funktion?

Med__call__()-metoden kan vi behandla klasserna som funktioner.

human = Human(1, 'Farhad')
human()

Vi kan uppnå den här funktionaliteten genom att tillhandahålla implementeringen av den magiska metoden __call__()-metoden i vår Human-klass.

Detta kommer att skriva ut:

Du försökte anropa
Argument: ()
Nyckelord Argument: {}
id=1 (<class ’int’>). name=Farhad (<class ’str’>)
Call completed

Genom att åsidosätta metoden __call__() kan vi nu implementera en dekorator för att returnera ett objekt som en funktion eller till och med anropa de bibliotek som accepterar funktioner som argument genom att skicka in de faktiska objekten.

Foto av Dollar Gill på Unsplash

Sammanfattning

Det här är ett ämne på avancerad nivå för Python-utvecklare och jag rekommenderar det till alla som är/eller tänker använda programmeringsspråket Python.

Denna artikel syftar till att förklara vad magiska metoder är och hur de kan användas för att bygga Pythonprogram. Den gav en översikt över de mest använda magiska metoderna i en anpassad klass, heltal, strängar, listor och datatyper i ordböcker.

Trots att varje metod i Python är offentlig är kodningskonventionen att omge alla privata metoder med dubbla understrykningar __<method>__()

Detta innebär att de magiska metoderna är avsedda att vara privata metoder. Det betyder också att den som anropar ett objekt inte ska anropa metoden direkt utan att metoden anropas internt av den klass som har den magiska metoden. Magiska metoder låter oss ha mer kontroll över hur vår applikation beter sig.

De magiska metoderna kan åsidosättas för att berika funktionaliteten och skapa anpassad logik som bäst passar verksamhetens behov.

Syftet med att beskriva de viktigaste magiska metoderna är att vi ska förstå om vi vill åsidosätta dessa metoder i våra anpassade klasser för att berika applikationerna.

Lämna ett svar

Din e-postadress kommer inte publiceras.