pats Python, demistificēts

Ja kādu laiku esat programmējis Python (objektorientētā programmēšana), tad noteikti esat saskāries ar metodēm self, kuru pirmais parametrs ir.

Vispirms mēģināsim saprast, kas ir šis atkārtotais pašparametrs.

Kas ir pats Python?

Objektorientētajā programmēšanā, kad mēs definējam klases metodes, mēs selfkatrā gadījumā izmantojam kā pirmo parametru. Apskatīsim tā saucamās klases definīciju 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")

Šajā gadījumā visām metodēm, ieskaitot __init__, pirmais parametrs ir kā self.

Mēs zinām, ka klase ir objektu projekts. Šo projektu var izmantot, lai izveidotu vairākus objektu numurus. Izveidosim divus dažādus objektus no iepriekš minētās klases.

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

selfAtslēgvārds tiek izmantots, lai pārstāvētu gadījumu (objektu) no attiecīgajā klasē. Šajā gadījumā divi Catobjekti cat1un cat2ir viņu pašu nameun ageatribūtus. Ja nebija paš argumentu, viena un tā pati klase nevarēja saturēt informāciju par abiem šiem objektiem.

Tomēr, tā kā klase ir tikai projekts, selfļauj piekļūt katra pitona objekta atribūtiem un metodēm. Tas ļauj katram objektam būt saviem atribūtiem un metodēm. Tādējādi pat ilgi pirms šo objektu izveidošanas mēs atsaucamies uz objektiem, selfdefinējot klasi.

Kāpēc sevi skaidri definē vienmēr?

Pat tad, kad mēs saprotam to lietošanu self, tas joprojām var šķist dīvaini, it īpaši programmētājiem, kas nāk no citām valodām, kas selfkā parametrs tiek nodots skaidri katru reizi, kad mēs definējam metodi. Kā raksta The Python Zen , " Skaidrs ir labāks nekā netiešs ".

Tātad, kāpēc mums tas jādara? Sākumā ņemsim vienkāršu piemēru. Mums ir Pointklase, kas nosaka metodi distanceattāluma aprēķināšanai no izcelsmes.

 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

Instantēsim šo klasi un atradīsim attālumu.

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

Iepriekš minētajā piemērā ir __init__()definēti trīs parametri, bet mēs tikko izturējām divus (6 un 8). Tāpat distance()nepieciešams viens, bet nulle argumentu tika nodota. Kāpēc Python nesūdzas par šī argumenta skaitļa neatbilstību?

Kas notiek iekšēji?

Point.distanceun p1.distanceiepriekš minētajā piemērā ir atšķirīgi un nav pilnīgi vienādi.

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

Mēs varam redzēt, ka pirmais ir funkcija, bet otrais - metode. Metodēm (Python) ir savdabīga lieta, ka pats objekts tiek nodots kā pirmais arguments attiecīgajai funkcijai.

Iepriekš minētā piemēra gadījumā metodes izsaukums p1.distance()faktiski ir ekvivalents Point.distance(p1).

Parasti, ja mēs izsaucam metodi ar dažiem argumentiem, tiek izsaukta atbilstošā klases funkcija, ievietojot metodes objektu pirms pirmā argumenta. Tātad, kaut kas līdzīgs obj.meth(args)kļūst Class.meth(obj, args). Zvanīšanas process ir automātisks, kamēr saņemšanas process nav (tā skaidri izteikts).

Tas ir iemesls, kāpēc klases pirmajam parametram jābūt pašam objektam. Šī parametra rakstīšana selfir tikai konvencija. Tas nav atslēgvārds, un tam nav īpašas nozīmes Python. Mēs varētu izmantot citus nosaukumus (piemēram, this), bet tas ir ļoti neiespējami. Lietojot citus nosaukumus, selfneuzskata, ka vairums izstrādātāju to noraida un pasliktina koda lasāmību ( lasāmība ir svarīga ).

No sevis var izvairīties

Tagad jums ir skaidrs, ka pats objekts (instance) tiek automātiski nodots kā pirmais arguments. Veicot statisku metodi, no šīs netiešās uzvedības var izvairīties . Apsveriet šādu vienkāršu piemēru:

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

Šeit @staticmethodir funkciju dekorators, kas padara stat_meth()statisku. Instantēsim šo klasi un izsauksim metodi.

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

No iepriekš minētā piemēra mēs varam redzēt, ka, izmantojot statisko metodi, netika novērsta netiešā objekta nodošanas kā pirmā argumenta uzvedība. Kopumā statiskās metodes darbojas tāpat kā vienkāršās vecās funkcijas (Tā kā visiem klases objektiem ir kopīgas statiskas metodes).

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

Sevi ir šeit, lai paliktu

Skaidrs selfnav tikai Python. Šī ideja tika aizgūta no Modula-3 . Šis ir lietošanas gadījums, kad tas kļūst noderīgs.

Python nav skaidras mainīgā deklarācijas. Viņi sāk darboties pēc pirmā uzdevuma. Izmantošana selfatvieglo instances atribūtu (un metožu) nošķiršanu no lokālajiem mainīgajiem.

Pirmajā piemērā self.x ir instances atribūts, bet x ir lokāls mainīgais. Viņi nav vienādi un atrodas dažādās nosaukumvietās.

Many have proposed to make self a keyword in Python, like this in C++ and Java. This would eliminate the redundant use of explicit self from the formal parameter list in methods.

While this idea seems promising, it is not going to happen. At least not in the near future. The main reason is backward compatibility. Here is a blog from the creator of Python himself explaining why the explicit self has to stay.

__init__() is not a constructor

One important conclusion that can be drawn from the information so far is that the __init__() method is not a constructor. Many naive Python programmers get confused with it since __init__() gets called when we create an object.

A closer inspection will reveal that the first parameter in __init__() is the object itself (object already exists). The function __init__() is called immediately after the object is created and is used to initialize it.

Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__(). A common signature of this method is:

 __new__(cls, *args, **kwargs)

When __new__() is called, the class itself is passed as the first argument automatically(cls).

Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.

Some important things to remember when implementing __new__() are:

  • __new__() is always called before __init__().
  • First argument is the class itself which is passed implicitly.
  • Always return a valid object from __new__(). Not mandatory, but its main use is to create and return an object.

Let's take a look at an example:

 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

Now, let's now instantiate it.

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

This example illustrates that __new__() is called before __init__(). We can also see that the parameter cls in __new__() is the class itself (Point). Finally, the object is created by calling the __new__() method on object base class.

In Python, object is the base class from which all other classes are derived. In the above example, we have done this using super().

Use __new__ or __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)

Izlases skrējiens:

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

Interesanti raksti...