Pokud už nějakou dobu programujete v Pythonu (objektově orientované programování), určitě jste narazili na metody, které mají self
jako 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 self
jako 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)
self
Klíčové slovo se používá k reprezentaci instance (objekt) dané třídy. V tomto případě jsou oba Cat
objekty cat1
a cat2
mají své vlastní name
a age
atributy. 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, self
umožň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 self
př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é self
se 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 Point
třídu, která definuje metodu distance
pro 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.distance
a p1.distance
ve 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 self
je 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ž self
je 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 @staticmethod
je 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í self
není 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í self
usnadň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 this
v C ++ a Javě. To by eliminovalo nadbytečné použití explicitního self
ze 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 object
je 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