já v Pythonu, Demystified

Pokud už nějakou dobu programujete v Pythonu (objektově orientované programování), určitě jste narazili na metody, které mají selfjako svůj první parametr.

Nejprve se pokusme pochopit, co je tento opakující se vlastní parametr.

Co je já v Pythonu?

V objektově orientovaném programování, kdykoli definujeme metody pro třídu, použijeme selfjako první parametr v každém případě. Pojďme se podívat na definici volané třídy 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 případě mají všechny metody, včetně __init__, první parametr jako self.

Víme, že tato třída je plánem pro objekty. Tento plán lze použít k vytvoření více čísel objektů. Vytvořme dva různé objekty z výše uvedené třídy.

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

selfKlíčové slovo se používá k reprezentaci instance (objekt) dané třídy. V tomto případě jsou oba Catobjekty cat1a cat2mají své vlastní namea ageatributy. Pokud neexistoval žádný vlastní argument, nemohla by stejná třída uchovávat informace pro oba tyto objekty.

Vzhledem k tomu, že třída je pouze plán, selfumožňuje přístup k atributům a metodám každého objektu v pythonu. To umožňuje každému objektu mít své vlastní atributy a metody. Dokonce tedy dlouho před vytvořením těchto objektů odkazujeme na objekty jako selfpři definování třídy.

Proč je vždy výslovně definováno já?

I když chápeme jeho použití self, může se to zdát zvláštní, zvláště programátorům pocházejícím z jiných jazyků, které selfse předávají jako parametr explicitně pokaždé, když definujeme metodu. Jak říká Zen of Python , „ Explicitní je lepší než implicitní “.

Proč to tedy musíme dělat? Vezměme si na začátek jednoduchý příklad. Máme Pointtřídu, která definuje metodu distancepro výpočet vzdálenosti od počátku.

 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

Pojďme nyní vytvořit instanci této třídy a zjistit vzdálenost.

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

Ve výše uvedeném příkladu __init__()definuje tři parametry, ale právě jsme předali dva (6 a 8). Podobně distance()vyžaduje jeden, ale bylo předáno nula argumentů. Proč si Python nestěžuje na tento nesoulad čísel argumentů?

Co se děje interně?

Point.distancea p1.distanceve výše uvedeném příkladu se liší a nejsou úplně stejné.

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

Vidíme, že první je funkce a druhá metoda. Zvláštní metodou (v Pythonu) je, že samotný objekt je předán jako první argument příslušné funkci.

V případě výše uvedeného příkladu je volání metody p1.distance()ve skutečnosti ekvivalentní s Point.distance(p1).

Obecně platí, že když zavoláme metodu s některými argumenty, je odpovídající funkce třídy volána umístěním objektu metody před první argument. Takže cokoli se obj.meth(args)stane Class.meth(obj, args). Proces volání je automatický, zatímco proces přijímání není (jeho explicitní).

Z tohoto důvodu musí být prvním parametrem funkce ve třídě samotný objekt. Zápis tohoto parametru selfje pouze konvence. Není to klíčové slovo a nemá v Pythonu žádný zvláštní význam. Mohli bychom použít jiná jména (jako this), ale je velmi nedoporučuje. Používání jiných názvů než selfje odsuzováno většinou vývojářů a zhoršuje čitelnost kódu ( čitelnost se počítá ).

Je možné se vyhnout já

Nyní je vám jasné, že samotný objekt (instance) je předán jako první argument automaticky. Tomuto implicitnímu chování se lze vyhnout při vytváření statické metody. Zvažte následující jednoduchý příklad:

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

Zde @staticmethodje dekorátor funkcí, který dělá stat_meth()statické. Pojďme vytvořit instanci této třídy a zavoláme metodu.

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

Z výše uvedeného příkladu vidíme, že při použití statické metody bylo zabráněno implicitnímu chování předávání objektu jako prvního argumentu. Celkově se statické metody chovají jako obyčejné staré funkce (protože všechny objekty třídy sdílejí statické metody).

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

Já je tu, aby zůstalo

Explicitní selfnení pro Python jedinečný. Tato myšlenka byla vypůjčena od Modula-3 . Následuje příklad použití, kdy to bude užitečné.

V Pythonu neexistuje žádná explicitní deklarace proměnné. Nastupují do akce na první úkol. Použití selfusnadňuje rozlišení mezi atributy instance (a metodami) od místních proměnných.

V prvním příkladu je self.x atribut instance, zatímco x je lokální proměnná. Nejsou stejné a leží v různých jmenných prostorech.

Mnoho lidí navrhlo, aby se z sebe v Pythonu stalo klíčové slovo, například thisv C ++ a Javě. To by eliminovalo nadbytečné použití explicitního selfze seznamu formálních parametrů v metodách.

I když se tato myšlenka jeví jako slibná, nestane se. Alespoň ne v blízké budoucnosti. Hlavním důvodem je zpětná kompatibilita. Zde je blog od samotného tvůrce Pythonu, který vysvětluje, proč explicitní já musí zůstat.

__init __ () není konstruktor

Jedním důležitým závěrem, který lze zatím vyvodit z informací, je, že __init__()metoda není konstruktor. Mnoho naivních programátorů Pythonu je s tím zmateno, protože __init__()je voláno, když vytváříme objekt.

Bližší prohlídka odhalí, že prvním parametrem __init__()je samotný objekt (objekt již existuje). Funkce __init__()je volána bezprostředně po vytvoření objektu a slouží k její inicializaci.

Technicky vzato, konstruktor je metoda, která vytváří samotný objekt. V Pythonu je tato metoda __new__(). Běžným podpisem této metody je:

 __new__(cls, *args, **kwargs)

Při __new__()volání je třída sama předána jako první argument automaticky ( cls).

Stejně jako já je cls opět jen konvence pojmenování. Kromě toho se * args a ** kwargs používají k převzetí libovolného počtu argumentů během volání metody v Pythonu.

Při implementaci __new__()je třeba pamatovat na několik důležitých věcí :

  • __new__()je vždy volána dříve __init__().
  • Prvním argumentem je samotná třída, která je předána implicitně.
  • Vždy vraťte platný objekt z __new__(). Není povinné, ale jeho hlavním použitím je vytvoření a vrácení objektu.

Podívejme se na příklad:

 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

Nyní to pojďme vytvořit instanci.

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

Tento příklad ukazuje, že __new__()se volá dříve __init__(). Vidíme také, že parametr cls in __new__()je samotná třída ( Point). Nakonec je objekt vytvořen voláním __new__()metody na základní třídě objektu .

V Pythonu objectje základní třída, ze které jsou odvozeny všechny ostatní třídy. Ve výše uvedeném příkladu jsme to provedli pomocí super ().

Použít __new__ nebo __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ázkový běh:

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

Zajímavé články...