Jei kurį laiką programavote „Python“ (objektinis programavimas), tikrai susidūrėte su metodais, kurių self
pirmasis parametras.
Pirmiausia pabandykime suprasti, kas yra šis pasikartojantis savęs parametras.
Kas yra „Python“ savastis?
Į objektinį programavimą, kai mes apibrėžiame klasės metodus self
, kiekvienu atveju mes naudojame kaip pirmąjį parametrą. Pažvelkime į vadinamosios klasės apibrėžimą 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")
Šiuo atveju visų metodų, įskaitant __init__
, pirmasis parametras yra self
.
Mes žinome, kad klasė yra objektų projektas. Šis projektas gali būti naudojamas kuriant kelis objektų skaičius. Sukurkime du skirtingus objektus iš minėtos klasės.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
self
Raktažodis naudojamas atstovauti egzempliorių (objektą), kad tam tikros klasės. Šiuo atveju du Cat
objektai cat1
ir cat2
turi savo name
bei age
atributus. Jei nebuvo savęs argumento, ta pati klasė negalėjo laikyti abiejų šių objektų informacijos.
Tačiau, kadangi klasė yra tik projektas, self
leidžia pasiekti kiekvieno python objekto atributus ir metodus. Tai leidžia kiekvienam objektui turėti savo atributus ir metodus. Taigi, dar ilgai prieš kurdami šiuos objektus, mes nurodome objektus kaip self
apibrėždami klasę.
Kodėl save aiškiai apibrėžta visada?
Net kai suprantame jų naudojimą self
, vis tiek gali atrodyti keista, ypač programuotojams, kilusiems iš kitų kalbų, kuris self
kaip parametras yra aiškiai perduodamas kiekvieną kartą, kai apibrėžiame metodą. Kaip rašoma „ Python Zen“ , „ aiškiau yra geriau nei numanomai “.
Taigi, kodėl mes turime tai daryti? Paimkime paprastą pavyzdį. Mes turime Point
klasę, apibrėžiančią metodą, distance
kaip apskaičiuoti atstumą nuo kilmės.
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
Išbandykime šią klasę ir raskime atstumą.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
Ankstesniame pavyzdyje __init__()
apibrėžiami trys parametrai, bet mes ką tik praėjome du (6 ir 8). Panašiai distance()
reikia vieno, bet nulis argumentų nebuvo pateiktas. Kodėl „Python“ nesiskundžia dėl šio argumento numerio neatitikimo?
Kas vyksta viduje?
Point.distance
ir p1.distance
aukščiau pateiktame pavyzdyje yra skirtingi ir nėra visiškai vienodi.
>>> type(Point.distance) >>> type(p1.distance)
Matome, kad pirmasis yra funkcija, o antrasis - metodas. Metodams („Python“) būdingas dalykas yra tai, kad pats objektas perduodamas kaip pirmasis argumentas atitinkamai funkcijai.
Ankstesnio pavyzdžio atveju metodo iškvietimas p1.distance()
iš tikrųjų yra lygiavertis Point.distance(p1)
.
Paprastai, kai mes iškviečiame metodą su kai kuriais argumentais, iškviečiama atitinkama klasės funkcija, metodo objektą padėjus prieš pirmąjį argumentą. Taigi, viskas, kas panašu, obj.meth(args)
tampa Class.meth(obj, args)
. Skambinimo procesas yra automatinis, o priėmimo procesas nėra (jo aiškus).
Tai yra priežastis, kodėl pirmasis funkcijos parametras klasėje turi būti pats objektas. Šio parametro rašymas self
yra tik sutartinis. Tai nėra raktinis žodis ir neturi ypatingos reikšmės „Python“. Galėtume naudoti kitus pavadinimus (pvz., this
), Bet tai labai nerekomenduojama. Naudojant kitus vardus nei self
dauguma nusiteikę, tai kenkia kodo įskaitomumui (skaitomumas ).
Savęs galima išvengti
Dabar jau aišku, kad pats objektas (egzempliorius) automatiškai perduodamas kaip pirmasis argumentas. Šio numanomo elgesio galima išvengti kuriant statinį metodą. Apsvarstykite šį paprastą pavyzdį:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Čia @staticmethod
yra funkcijų dekoratorius, kuris daro stat_meth()
statinį. Iškreipkime šią klasę ir pakvieskime metodą.
>>> a = A() >>> a.stat_meth() Look no self was passed
Iš pirmiau pateikto pavyzdžio matome, kad naudojant statinį metodą buvo išvengta numanomo objekto perdavimo kaip pirmo argumento elgesio. Apskritai, statiniai metodai elgiasi kaip paprastos senos funkcijos (Kadangi visi klasės objektai naudojasi statiniais metodais).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Aš esu čia, kad likčiau
„Explicit self
“ nėra būdingas tik „Python“. Ši idėja pasiskolinta iš „ Modula-3“ . Toliau pateikiamas naudojimo atvejis, kai tai tampa naudinga.
„Python“ nėra aiškaus kintamojo deklaravimo. Jie pradeda veikti atlikdami pirmą užduotį. Naudojant „“, self
lengviau atskirti egzempliorių atributus (ir metodus) nuo vietinių kintamųjų.
Pirmame pavyzdyje „self.x“ yra egzemplioriaus atributas, o x yra vietinis kintamasis. Jie nėra vienodi ir guli skirtingose vardų erdvėse.
Daugelis pasiūlė padaryti „Python“ raktinį žodį, pvz. this
, „C ++“ ir „Java“. Tai pašalintų nereikalingą aiškių self
metodų naudojimą iš oficialaus parametrų sąrašo.
Nors ši idėja atrodo perspektyvi, ji neįvyks. Bent jau artimiausiu metu ne. Pagrindinė priežastis yra atgalinis suderinamumas. Čia yra paties „Python“ kūrėjo tinklaraštis, kuriame paaiškinta, kodėl aiškus „aš“ turi likti.
__init __ () nėra konstruktorius
Viena svarbi išvada, kurią galima padaryti iš iki šiol pateiktos informacijos, yra ta, kad __init__()
metodas nėra konstruktorius. Daugelis naivių „Python“ programuotojų su ja susipainioja, nes juos __init__()
iškviečia, kai kuriame objektą.
Atidžiau apžiūrėjus paaiškės, kad pirmasis parametras __init__()
yra pats objektas (objektas jau egzistuoja). Funkcija __init__()
iškviečiama iškart po objekto sukūrimo ir naudojama jam inicializuoti.
Kalbant techniškai, konstruktorius yra metodas, sukuriantis patį objektą. „Python“ šis metodas yra __new__()
. Bendras šio metodo parašas yra:
__new__(cls, *args, **kwargs)
Kai __new__()
iškviečiama, pati klasė perduodama kaip pirmasis argumentas automatiškai ( cls
).
Vėlgi, kaip ir aš, „cls“ yra tik vardų suteikimo tvarka. Be to, * args ir ** kwargs naudojami norint pasirinkti savavališkai daug argumentų metodo iškvietimų metu „Python“.
Keli svarbūs dalykai, kuriuos reikia atsiminti įgyvendinant __new__()
:
__new__()
visada skambinama anksčiau__init__()
.- Pirmasis argumentas yra pati klasė, kuri perduodama netiesiogiai.
- Visada grąžinkite galiojantį objektą iš
__new__()
. Nėra privaloma, tačiau pagrindinė jo paskirtis yra sukurti ir grąžinti objektą.
Pažvelkime į pavyzdį:
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
Dabar tai išaiškinkime.
>>> p2 = Point(3,4) From new (3, 4) () From init
Šis pavyzdys iliustruoja tai, __new__()
kas buvo vadinama anksčiau __init__()
. Taip pat galime pamatyti, kad parametras cls __new__()
yra pati klasė ( Point
). Galiausiai objektas sukuriamas iškviečiant __new__()
metodą objekto pagrindo klasėje.
„Python“ object
yra pagrindinė klasė, iš kurios gaunamos visos kitos klasės. Ankstesniame pavyzdyje mes tai padarėme naudodami super ().
Naudoti __new__ ar __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)
Pavyzdinis bandymas:
>>> 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