itse Pythonissa, demystifioitu

Jos olet ohjelmoinut Pythonissa (olio-ohjelmointi) jonkin aikaa, olet ehdottomasti törmännyt menetelmiin, selfjoiden ensimmäinen parametri on.

Yritetään ensin ymmärtää, mikä tämä toistuva itseparametri on.

Mikä on itse Pythonissa?

Kun määritämme olio-ohjelmoinnissa luokalle menetelmiä, käytämme selfkussakin tapauksessa ensimmäisenä parametrina. Katsotaanpa nimeltään luokan määritelmää 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")

Tässä tapauksessa kaikkien menetelmien, mukaan lukien __init__, ensimmäinen parametri on self.

Tiedämme, että luokka on esineiden suunnitelma. Tätä suunnitelmaa voidaan käyttää luomaan useita objekteja. Luodaan kaksi erilaista objektia yllä olevasta luokasta.

 cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)

selfAvainsanaa käytetään edustamaan esimerkiksi (kohde) annettua luokkaa. Tässä tapauksessa kaksi Catesineitä cat1ja cat2on omat nameja ageominaisuuksia. Jos itseargumenttia ei ollut, sama luokka ei voinut säilyttää molempien objektien tietoja.

Koska luokka on kuitenkin vain suunnitelma, selfsallii pääsyn pythonin kunkin objektin määritteisiin ja menetelmiin. Näin jokaisella objektilla voi olla omat määritteensä ja menetelmänsä. Siten, jopa kauan ennen näiden objektien luomista, viittaamme kohteisiin kuten selfluokkaa määriteltäessä.

Miksi itse määritellään nimenomaan joka kerta?

Vaikka ymmärrämme sen käytön self, se voi silti tuntua oudolta, etenkin muilta kieliltä tuleville ohjelmoijille, joka selfvälitetään parametrina nimenomaisesti joka kerta, kun määritämme menetelmän. Kuten The Zen of Python kertoo, " eksplisiittinen on parempi kuin implisiittinen ".

Joten, miksi meidän on tehtävä tämä? Otetaan aluksi yksinkertainen esimerkki. Meillä on Pointluokka, joka määrittelee menetelmän distanceetäisyyden laskemiseksi alkuperästä.

 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

Tehkäämme nyt tämä luokka ja etsimme etäisyys.

 >>> p1 = Point(6,8) >>> p1.distance() 10.0

Yllä olevassa esimerkissä __init__()määritetään kolme parametria, mutta ohitimme juuri kaksi (6 ja 8). Samoin distance()vaatii yhden, mutta nolla argumenttia välitettiin. Miksi Python ei valittaa tästä argumenttinumeron ristiriitaisuudesta?

Mitä tapahtuu sisäisesti?

Point.distanceja p1.distanceyllä olevassa esimerkissä ovat erilaiset eivätkä täysin samat.

 >>> type(Point.distance) >>> type(p1.distance) 

Voimme nähdä, että ensimmäinen on funktio ja toinen menetelmä. Menetelmien (Pythonissa) erikoinen asia on, että itse objekti välitetään ensimmäisenä argumenttina vastaavalle funktiolle.

Yllä olevan esimerkin tapauksessa menetelmän kutsu p1.distance()vastaa itse asiassa Point.distance(p1).

Yleensä kun kutsumme menetelmää joidenkin argumenttien kanssa, vastaava luokkafunktio kutsutaan sijoittamalla metodin objekti ensimmäisen argumentin eteen. Joten kaikenlainen obj.meth(args)tulee Class.meth(obj, args). Soittoprosessi on automaattinen, kun taas vastaanottoprosessi ei ole (sen nimenomainen).

Tästä syystä luokan funktion ensimmäisen parametrin on oltava objekti itse. Tämän parametrin kirjoittaminen selfon vain käytäntö. Se ei ole avainsana eikä sillä ole erityistä merkitystä Pythonissa. Voisimme käyttää muita nimiä (kuten this), mutta sitä ei suositella. selfUseimmat kehittäjät paheksuvat muiden kuin nimien käyttöä ja heikentävät koodin luettavuutta ( Luettavuus laskee ).

Itse voidaan välttää

Tähän mennessä olet selvä, että itse objekti (instanssi) välitetään automaattisesti ensimmäisenä argumenttina. Tämä implisiittinen käyttäytyminen voidaan välttää staattista menetelmää tehtäessä . Harkitse seuraavaa yksinkertaista esimerkkiä:

 class A(object): @staticmethod def stat_meth(): print("Look no self was passed")

Tässä @staticmethodon toiminnon sisustaja, joka tekee stat_meth()staattiseksi. Tehkäämme tämä luokka ja kutsumme menetelmä.

 >>> a = A() >>> a.stat_meth() Look no self was passed

Yllä olevasta esimerkistä voimme nähdä, että kohteen välittäminen implisiittiseksi käytökseksi ensimmäisenä argumenttina vältettiin staattista menetelmää käytettäessä. Kaiken kaikkiaan staattiset menetelmät käyttäytyvät kuin tavalliset vanhat toiminnot (Koska kaikki luokan objektit jakavat staattisia menetelmiä).

 >>> type(A.stat_meth) >>> type(a.stat_meth) 

Itse on täällä jäädäkseen

Explicit selfei ole ainutlaatuinen Pythonille. Tämä idea on lainattu Modula-3: lta . Seuraava on käyttötapaus, jossa siitä on hyötyä.

Pythonissa ei ole nimenomaista muuttujailmoitusta. Ne alkavat toimia ensimmäisessä tehtävässä. Käytön selfavulla on helpompaa erottaa ilmentymämääritteet (ja menetelmät) paikallisista muuttujista.

Ensimmäisessä esimerkissä self.x on ilmentymäattribuutti, kun taas x on paikallinen muuttuja. Ne eivät ole samoja ja ne sijaitsevat eri nimitiloissa.

Monet ovat ehdottaneet itsensä tekemistä avainsanaksi Pythonissa, kuten thisC ++ ja Java. Tämä eliminoisi selfmuodollisen parametriluettelon nimenomaisen käytön menetelmissä.

Vaikka tämä idea näyttää lupaavalta, sitä ei tule tapahtumaan. Ainakin ei lähitulevaisuudessa. Tärkein syy on taaksepäin yhteensopivuus. Tässä on Pythonin luojan blogi, jossa selitetään, miksi nimenomaisen itsen on pysyttävä.

__init __ () ei ole rakentaja

Yksi tärkeä johtopäätös, joka voidaan tehdä tähänastisesta tiedosta, on se, että __init__()menetelmä ei ole konstruktori. Monet naiivit Python-ohjelmoijat sekoittuvat siihen, koska __init__()heidät kutsutaan, kun luomme objektin.

Tarkempi tarkastelu paljastaa, että ensimmäinen parametri __init__()on itse objekti (objekti on jo olemassa). Toimintoa __init__()kutsutaan välittömästi sen jälkeen, kun objekti on luotu, ja sitä käytetään sen alustamiseen.

Teknisesti konstruktori on menetelmä, joka luo itse objektin. Pythonissa tämä menetelmä on __new__(). Tämän menetelmän yleinen allekirjoitus on:

 __new__(cls, *args, **kwargs)

Kun __new__()kutsutaan, luokka itse välitetään ensimmäisenä argumenttina automaattisesti ( cls).

Jälleen, kuten itse, cls on vain nimeämiskäytäntö. Lisäksi * args ja ** kwargs käytetään mielivaltaisen määrän argumentteja metodikutsujen aikana Pythonissa.

Joitakin tärkeitä asioita, jotka on muistettava toteutuksessa __new__():

  • __new__()soitetaan aina aiemmin __init__().
  • Ensimmäinen argumentti on luokka itse, joka hyväksytään implisiittisesti.
  • Palauta aina kelvollinen esine kohteesta __new__(). Ei pakollinen, mutta sen pääasiallinen käyttötarkoitus on luoda ja palauttaa objekti.

Katsotaanpa esimerkkiä:

 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

Tehdään nyt se heti.

 >>> p2 = Point(3,4) From new (3, 4) () From init

Tämä esimerkki kuvaa sitä, mitä __new__()kutsutaan aikaisemmin __init__(). Voimme myös nähdä, että parametri cls __new__()on luokka itse ( Point). Lopuksi objekti luodaan kutsumalla __new__()menetelmä objektin perusluokkaan.

Pythonissa objecton perusluokka, josta kaikki muut luokat on johdettu. Yllä olevassa esimerkissä olemme tehneet tämän super (): lla.

Käytä __uusi__ tai __init__?

Olet ehkä nähnyt __init__()hyvin usein, mutta niiden käyttö __new__()on harvinaista. Tämä johtuu siitä, että suurimman osan ajasta sinun ei tarvitse korvata sitä. Yleensä __init__()sitä käytetään uuden luodun objektin alustamiseen, kun taas __new__()sitä käytetään objektin luomisen hallintaan.

Voimme käyttää myös __new__()kohteen attribuuttien alustamiseen, mutta loogisesti sen pitäisi olla sisällä __init__().

Yksi käytännön käyttö __new__()voisi kuitenkin olla luokasta luotujen objektien määrän rajoittaminen.

Oletetaan, että halusimme luokan SqPointesiintymien luomiseksi, jotka edustavat neliön neljää kärkeä . Voimme periä edellisestä luokastamme Point(tämän artikkelin toinen esimerkki) ja käyttää __new__()tämän rajoituksen toteuttamiseen. Tässä on esimerkki luokan rajoittamisesta siten, että siinä on vain neljä esiintymää.

 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)

Näyte ajo:

 >>> 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

Mielenkiintoisia artikkeleita...