Hvis du har programmeret i Python (objektorienteret programmering) i nogen tid, er du bestemt stødt på metoder, der har self
som deres første parameter.
Lad os først prøve at forstå, hvad denne tilbagevendende selvparameter er.
Hvad er selv i Python?
I objektorienteret programmering, når vi definerer metoder til en klasse, bruger vi self
som den første parameter i hvert tilfælde. Lad os se på definitionen af en klasse kaldet Cat
.
class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")
I dette tilfælde har alle metoderne inklusive __init__
den første parameter som self
.
Vi ved, at klassen er en plan for objekterne. Denne tegning kan bruges til at oprette flere antal objekter. Lad os oprette to forskellige objekter fra ovenstående klasse.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
Den self
nøgleord bruges til at repræsentere en instans (objekt) af given klasse. I dette tilfælde de to Cat
objekter cat1
og cat2
har deres egen name
og age
attributter. Hvis der ikke var noget selvargument, kunne den samme klasse ikke indeholde oplysningerne for begge disse objekter.
Men da klassen kun er en tegning, self
giver den adgang til attributterne og metoderne for hvert objekt i python. Dette gør det muligt for hvert objekt at have sine egne attributter og metoder. Således længe før oprettelse af disse objekter henviser vi til objekterne, self
mens vi definerer klassen.
Hvorfor defineres selv eksplicit hver gang?
Selv når vi forstår brugen af self
, kan det stadig virke underligt, især for programmører, der kommer fra andre sprog, der self
sendes som en parameter eksplicit hver gang vi definerer en metode. Som Zen of Python siger, " Eksplicit er bedre end implicit ".
Så hvorfor skal vi gøre dette? Lad os tage et simpelt eksempel til at begynde med. Vi har en Point
klasse, der definerer en metode distance
til at beregne afstanden fra oprindelsen.
class Point(object): def __init__(self,x = 0,y = 0): self.x = x self.y = y def distance(self): """Find distance from origin""" return (self.x**2 + self.y**2) ** 0.5
Lad os nu instantiere denne klasse og finde afstanden.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
I ovenstående eksempel __init__()
defineres tre parametre, men vi har lige bestået to (6 og 8). Tilsvarende distance()
kræver et, men nul argumenter blev bestået. Hvorfor klager Python ikke over, at dette argumentnummer ikke stemmer overens?
Hvad sker der internt?
Point.distance
og p1.distance
i ovenstående eksempel er forskellige og ikke nøjagtigt de samme.
>>> type(Point.distance) >>> type(p1.distance)
Vi kan se, at den første er en funktion, og den anden er en metode. En ejendommelig ting ved metoder (i Python) er, at selve objektet sendes som det første argument til den tilsvarende funktion.
I tilfældet med ovenstående eksempel p1.distance()
svarer metodeopkaldet faktisk til Point.distance(p1)
.
Generelt når vi kalder en metode med nogle argumenter, kaldes den tilsvarende klassefunktion ved at placere metodens objekt før det første argument. Så hvad som helst obj.meth(args)
bliver Class.meth(obj, args)
. Opkaldsprocessen er automatisk, mens modtagelsesprocessen ikke er (dens eksplicit).
Dette er grunden til, at den første parameter for en funktion i klassen skal være selve objektet. At skrive denne parameter som self
kun er en konvention. Det er ikke et nøgleord og har ingen særlig betydning i Python. Vi kunne bruge andre navne (som this
), men det er meget modløs. Brug af andre navne end self
de fleste udviklere ser ud af og nedbryder læsbarheden af koden ( Læsbarhed tæller ).
Selv kan undgås
Nu er du klar over, at selve objektet (instansen) sendes videre som det første argument automatisk. Denne implicitte opførsel kan undgås, mens man laver en statisk metode. Overvej følgende enkle eksempel:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Her @staticmethod
er en funktion dekoratør, der gør stat_meth()
statisk. Lad os instantiere denne klasse og kalde metoden.
>>> a = A() >>> a.stat_meth() Look no self was passed
Fra ovenstående eksempel kan vi se, at den implicitte opførsel af at sende objektet som det første argument blev undgået, mens der blev brugt en statisk metode. Alt i alt opfører statiske metoder sig som de almindelige gamle funktioner (Da alle objekterne i en klasse deler statiske metoder).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Selv er der for at blive
Den eksplicitte self
er ikke unik for Python. Denne idé blev lånt fra Modula-3 . Følgende er en brugssag, hvor det bliver nyttigt.
Der er ingen eksplicit variabelerklæring i Python. De springer ud i handling på den første opgave. Brugen af self
gør det lettere at skelne mellem instansattributter (og metoder) fra lokale variabler.
I det første eksempel er self.x en instansattribut, mens x er en lokal variabel. De er ikke de samme, og de ligger i forskellige navneområder.
Mange har foreslået at gøre sig selv til et nøgleord i Python, som this
i C ++ og Java. Dette ville eliminere den overflødige brug af eksplicit self
fra den formelle parameterliste i metoder.
Selvom denne idé virker lovende, vil den ikke ske. I det mindste ikke i den nærmeste fremtid. Hovedårsagen er bagudkompatibilitet. Her er en blog fra skaberen af Python selv, der forklarer, hvorfor det eksplicitte selv skal forblive.
__init __ () er ikke en konstruktør
En vigtig konklusion, der hidtil kan drages af informationen, er, at __init__()
metoden ikke er en konstruktør. Mange naive Python-programmører bliver forvirrede med det, da de __init__()
bliver kaldt, når vi opretter et objekt.
En nærmere inspektion vil afsløre, at den første parameter i __init__()
er selve objektet (objektet eksisterer allerede). Funktionen __init__()
kaldes straks efter at objektet er oprettet og bruges til at initialisere det.
Teknisk set er en konstruktør en metode, der skaber selve objektet. I Python er denne metode __new__()
. En fælles underskrift af denne metode er:
__new__(cls, *args, **kwargs)
Når __new__()
kaldes, overføres klassen automatisk som det første argument automatisk ( cls
).
Igen er ligesom mig selv en navngivningskonvention. Desuden bruges * args og ** kwargs til at tage et vilkårligt antal argumenter under metodeopkald i Python.
Nogle vigtige ting at huske, når du implementerer, __new__()
er:
__new__()
kaldes altid før__init__()
.- Første argument er selve klassen, der passeres implicit.
- Returner altid et gyldigt objekt fra
__new__()
. Ikke obligatorisk, men dets vigtigste anvendelse er at oprette og returnere et objekt.
Lad os se på et eksempel:
class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y
Lad os nu instantiere det.
>>> p2 = Point(3,4) From new (3, 4) () From init
Dette eksempel illustrerer det, __new__()
der kaldes før __init__()
. Vi kan også se, at parameteren cls __new__()
er selve klassen ( Point
). Endelig oprettes objektet ved at kalde __new__()
metoden på objektbaseklassen .
I Python object
er basisklassen, hvorfra alle andre klasser stammer. I ovenstående eksempel har vi gjort dette ved hjælp af super ().
Brug __new__ eller __init__?
You might have seen __init__()
very often but the use of __new__()
is rare. This is because most of the time you don't need to override it. Generally, __init__()
is used to initialize a newly created object while __new__()
is used to control the way an object is created.
We can also use __new__()
to initialize attributes of an object, but logically it should be inside __init__()
.
One practical use of __new__()
, however, could be to restrict the number of objects created from a class.
Suppose we wanted a class SqPoint
for creating instances to represent the four vertices of a square. We can inherit from our previous class Point
(the second example in this article) and use __new__()
to implement this restriction. Here is an example to restrict a class to have only four instances.
class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)
En prøvekørsel:
>>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects