ja v Pythone, Demystifikovaný

Ak už nejaký čas programujete v Pythone (objektovo orientované programovanie), potom ste sa určite stretli s metódami, ktoré majú selfako prvý parameter.

Najprv sa pokúsme pochopiť, čo je tento opakujúci sa vlastný parameter.

Čo je ja v Pythone?

V objektovo orientovanom programovaní vždy, keď definujeme metódy pre triedu, použijeme selfako prvý parameter vždy. Pozrime sa na definíciu triedy s názvom 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")

V tomto prípade majú všetky metódy vrátane __init__prvý parameter ako self.

Vieme, že táto trieda je vzorom pre objekty. Tento plán je možné použiť na vytvorenie viacnásobného počtu objektov. Vytvorme dva rôzne objekty z vyššie uvedenej triedy.

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

selfKľúčové slovo sa používa na reprezentáciu inštancie (objekt) danej triedy. V tomto prípade dva Catobjekty cat1a cat2majú svoje vlastné namea ageatribúty. Ak by neexistoval argument seba, rovnaká trieda by nemohla uchovávať informácie pre oba tieto objekty.

Pretože je však trieda iba modrotlačou, selfumožňuje prístup k atribútom a metódam každého objektu v pythone. To umožňuje každému objektu mať svoje vlastné atribúty a metódy. Preto aj dlho pred vytvorením týchto objektov odkazujeme na objekty ako selfpri definovaní triedy.

Prečo je ja výslovne definované vždy?

Aj keď pochopíme jeho použitie self, môže sa to zdať zvláštne, najmä programátorom pochádzajúcim z iných jazykov, ktorý selfsa odovzdáva ako parameter explicitne zakaždým, keď definujeme metódu. Ako hovorí Zen of Python , „ Explicitné je lepšie ako implicitné “.

Prečo to teda musíme robiť? Vezmime si na začiatok jednoduchý príklad. Máme Pointtriedu, ktorá definuje metódu distancena výpočet vzdialenosti od počiatku.

 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

Poďme teraz vytvoriť inštanciu tejto triedy a zistiť vzdialenosť.

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

Vo vyššie uvedenom príklade __init__()definuje tri parametre, ale práve sme minuli dva (6 a 8). Podobne distance()vyžaduje jeden, ale nula argumentov. Prečo sa Python nesťažuje na tento nesúlad čísel argumentov?

Čo sa deje vnútorne?

Point.distancea p1.distancevo vyššie uvedenom príklade sú odlišné a nie úplne rovnaké.

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

Vidíme, že prvý je funkcia a druhý je metóda. Zvláštnosťou metód (v Pythone) je to, že samotný objekt je odovzdaný ako prvý argument zodpovedajúcej funkcii.

V prípade vyššie uvedeného príkladu je volanie metódy p1.distance()vlastne ekvivalentné s Point.distance(p1).

Spravidla, keď voláme metódu s niektorými argumentmi, funkcia príslušnej triedy sa volá umiestnením objektu metódy pred prvý argument. Takže všetko sa obj.meth(args)stane Class.meth(obj, args). Proces volania je automatický, zatiaľ čo proces prijímania nie je (explicitný).

To je dôvod, prečo prvým parametrom funkcie v triede musí byť samotný objekt. Zápis tohto parametra selfje iba konvenciou. Nie je to kľúčové slovo a nemá v Pythone žiadny zvláštny význam. Mohli by sme použiť iné mená (ako this), ale veľmi sa to neodporúča. Používanie iných názvov ako selfsa väčšina vývojárov zamýšľa a zhoršuje čitateľnosť kódu ( počet čitateľností ).

Ja sa dá vyhnúť

Teraz je už jasné, že samotný objekt (inštancia) sa odovzdáva ako prvý argument automaticky. Tomuto implicitnému správaniu sa dá vyhnúť pri vytváraní statickej metódy. Uvažujme o nasledujúcom jednoduchom príklade:

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

Tu @staticmethodje dekorátor funkcií, ktorý robí stat_meth()statický. Poďme vytvoriť inštanciu tejto triedy a zavolajme metódu.

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

Z vyššie uvedeného príkladu vidíme, že pri použití statickej metódy sa zabránilo implicitnému správaniu sa k objektu ako prvému argumentu. Celkovo sa statické metódy správajú ako obyčajné staré funkcie (keďže všetky objekty triedy zdieľajú statické metódy).

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

Ja je tu na to, aby zostalo

Explicitný selfnie je pre Python jedinečný. Táto myšlienka bola vypožičaná od spoločnosti Modula-3 . Nasleduje prípad použitia, keď to bude užitočné.

V Pythone neexistuje žiadna výslovná deklarácia premennej. Do akcie nastupujú pri prvom zadaní. Použitie selfvýrazu usnadňuje rozlíšenie medzi atribútmi inštancie (a metódami) od miestnych premenných.

V prvom príklade je self.x atribút inštancie, zatiaľ čo x je lokálna premenná. Nie sú rovnaké a ležia v rôznych menných priestoroch.

Mnoho ľudí navrhlo, aby sa zo seba stalo kľúčové slovo v Pythone, napríklad thisv C ++ a Java. To by eliminovalo nadbytočné použitie explicitného selfzo zoznamu formálnych parametrov v metódach.

Aj keď sa zdá byť táto myšlienka sľubná, nestane sa tak. Aspoň nie v blízkej budúcnosti. Hlavným dôvodom je spätná kompatibilita. Tu je blog od samotného tvorcu Pythonu, ktorý vysvetľuje, prečo musí explicitné ja zostať.

__init __ () nie je konštruktor

Z doterajších informácií možno vyvodiť jeden dôležitý záver, že __init__()metóda nie je konštruktor. Mnoho naivných programátorov Pythonu je s tým zmätených, pretože __init__()sa volajú pri vytváraní objektu.

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)

Ukážka cyklu:

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

Zaujímavé články...