eu în Python, Demystified

Dacă ați programat în Python (programare orientată obiect) de ceva timp, atunci ați întâlnit cu siguranță metode care au selfca prim parametru.

Să încercăm mai întâi să înțelegem ce este acest parametru de sine recurent.

Ce este sinele în Python?

În programarea orientată pe obiecte, ori de câte ori definim metode pentru o clasă, folosim selfca prim parametru în fiecare caz. Să ne uităm la definiția unei clase numită 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")

În acest caz , toate metodele, inclusiv __init__, au primul parametru ca self.

Știm că clasa este un plan pentru obiecte. Acest plan poate fi folosit pentru a crea mai multe numere de obiecte. Să creăm două obiecte diferite din clasa de mai sus.

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

Cuvântul selfcheie este folosit pentru a reprezenta o instanță (obiect) din clasa dată. În acest caz, cele două Catobiecte cat1și cat2propriile lor nameși ageatributele lor. Dacă nu a existat nici un argument de sine, aceeași clasă nu ar putea deține informațiile pentru ambele obiecte.

Cu toate acestea, deoarece clasa este doar un plan, selfpermite accesul la atributele și metodele fiecărui obiect din python. Acest lucru permite fiecărui obiect să aibă propriile sale atribute și metode. Astfel, chiar cu mult înainte de a crea aceste obiecte, referim obiectele ca în selftimp ce definim clasa.

De ce este definit de sine în mod explicit de fiecare dată?

Chiar și atunci când înțelegem utilizarea self, poate părea ciudat, în special pentru programatorii care provin din alte limbi, că selfeste transmis ca parametru în mod explicit de fiecare dată când definim o metodă. După cum spune Zenul Python , „ Explicit este mai bun decât implicit ”.

Deci, de ce trebuie să facem acest lucru? Să luăm un exemplu simplu pentru început. Avem o Pointclasă care definește o metodă distancepentru a calcula distanța de la origine.

 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

Să instanțăm acum această clasă și să găsim distanța.

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

În exemplul de mai sus, __init__()definește trei parametri, dar tocmai am trecut doi (6 și 8). În mod similar distance()necesită unul, dar au fost adoptate zero argumente. De ce Python nu se plânge de această nepotrivire a numărului de argumente?

Ce se întâmplă intern?

Point.distanceiar p1.distanceîn exemplul de mai sus sunt diferite și nu exact aceleași.

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

Putem vedea că prima este o funcție și a doua este o metodă. Un lucru particular al metodelor (în Python) este că obiectul în sine este trecut ca primul argument funcției corespunzătoare.

În cazul exemplului de mai sus, apelul metodei p1.distance()este de fapt echivalent cu Point.distance(p1).

În general, când apelăm o metodă cu unele argumente, funcția de clasă corespunzătoare este apelată prin plasarea obiectului metodei înaintea primului argument. Deci orice obj.meth(args)devine Class.meth(obj, args). Procesul de apelare este automat, în timp ce procesul de primire nu este (explicit).

Acesta este motivul pentru care primul parametru al unei funcții din clasă trebuie să fie obiectul în sine. Scrierea acestui parametru selfeste doar o convenție. Nu este un cuvânt cheie și nu are o semnificație specială în Python. Am putea folosi alte nume (cum ar fi this), dar este foarte descurajat. Utilizarea altor nume decât cele selfignorate de majoritatea dezvoltatorilor și degradează lizibilitatea codului ( Readability counts ).

Sinele poate fi evitat

Până acum, sunteți clar că obiectul (instanța) în sine este transmis automat ca primul argument. Acest comportament implicit poate fi evitat în timp ce se realizează o metodă statică . Luați în considerare următorul exemplu simplu:

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

Iată @staticmethodun decorator de funcții care face stat_meth()static. Să instanțiem această clasă și să apelăm metoda.

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

Din exemplul de mai sus, putem vedea că comportamentul implicit al transmiterii obiectului ca prim argument a fost evitat în timpul utilizării unei metode statice. Una peste alta, metodele statice se comportă ca niște funcții vechi simple (Deoarece toate obiectele unei clase partajează metode statice).

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

Sinele este aici pentru a rămâne

Explicitul selfnu este unic pentru Python. Această idee a fost împrumutată de la Modula-3 . Următorul este un caz de utilizare în care devine util.

Nu există nicio declarație explicită de variabilă în Python. Ele încep să acționeze la prima sarcină. Utilizarea lui selfface mai ușoară distincția între atributele de instanță (și metode) de variabilele locale.

În primul exemplu, self.x este un atribut de instanță, în timp ce x este o variabilă locală. Nu sunt la fel și se află în spații de nume diferite.

Mulți s-au propus să facă din sine un cuvânt cheie în Python, cum ar fi thisîn C ++ și Java. Aceasta ar elimina utilizarea redundantă a explicit selfdin lista formală de parametri în metode.

Deși această idee pare promițătoare, nu se va întâmpla. Cel puțin nu în viitorul apropiat. Motivul principal este compatibilitatea inversă. Iată un blog de la creatorul Python însuși care explică de ce trebuie să rămână sinele explicit.

__init __ () nu este un constructor

O concluzie importantă care poate fi extrasă din informațiile de până acum este că __init__()metoda nu este un constructor. Mulți programatori naivi Python se confundă cu acesta de când __init__()sunt chemați atunci când creăm un obiect.

O inspecție mai atentă va arăta că primul parametru __init__()este obiectul în sine (obiectul există deja). Funcția __init__()este apelată imediat după crearea obiectului și este utilizată pentru inițializarea acestuia.

Din punct de vedere tehnic, un constructor este o metodă care creează obiectul în sine. În Python, această metodă este __new__(). O semnătură comună a acestei metode este:

 __new__(cls, *args, **kwargs)

Când __new__()este apelat, clasa în sine este trecută automat ca primul argument ( cls).

Din nou, ca și sine, cls este doar o convenție de numire. În plus, * args și ** kwargs sunt folosite pentru a lua un număr arbitrar de argumente în timpul apelurilor de metode în Python.

Câteva lucruri importante de reținut atunci când implementați __new__()sunt:

  • __new__()este numit întotdeauna înainte __init__().
  • Primul argument este clasa în sine care este transmisă implicit.
  • Întoarceți întotdeauna un obiect valid de la __new__(). Nu este obligatoriu, dar principala sa utilizare este crearea și returnarea unui obiect.

Să aruncăm o privire la un exemplu:

 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

Acum, să o instanțiem acum.

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

Acest exemplu ilustrează că __new__()se numește înainte __init__(). De asemenea, putem vedea că parametrul cls __new__()este clasa în sine ( Point). În cele din urmă, obiectul este creat apelând __new__()metoda la clasa de bază a obiectului .

În Python, objecteste clasa de bază din care derivă toate celelalte clase. În exemplul de mai sus, am făcut acest lucru folosind super ().

Folosiți __new__ sau __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)

Un exemplu de rulare:

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

Articole interesante...