Jeśli od jakiegoś czasu zajmujesz się programowaniem w języku Python (programowanie obiektowe), to na pewno trafiłeś na metody, które mają self
jako pierwszy parametr.
Najpierw spróbujmy zrozumieć, czym jest ten powtarzający się parametr siebie.
Co to jest self w Pythonie?
W programowaniu obiektowym, ilekroć definiujemy metody dla klasy, self
w każdym przypadku używamy ich jako pierwszego parametru. Spójrzmy na definicję klasy o nazwie 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")
W tym przypadku wszystkie metody, w tym __init__
, mają pierwszy parametr jako self
.
Wiemy, że klasa jest planem obiektów. Ten plan można wykorzystać do stworzenia wielu liczb obiektów. Stwórzmy dwa różne obiekty z powyższej klasy.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
Słowo self
kluczowe służy do reprezentowania instancji (obiektu) danej klasy. W tym przypadku oba Cat
obiekty cat1
i cat2
mają własne name
i age
atrybuty. Gdyby nie było samoargumentowania, ta sama klasa nie mogłaby przechowywać informacji dla obu tych obiektów.
Jednak ponieważ klasa jest tylko planem, self
umożliwia dostęp do atrybutów i metod każdego obiektu w Pythonie. Dzięki temu każdy obiekt może mieć własne atrybuty i metody. Dlatego nawet na długo przed utworzeniem tych obiektów odwołujemy się do obiektów tak self
, jak podczas definiowania klasy.
Dlaczego za każdym razem jest określane samo siebie?
Nawet jeśli zrozumiemy użycie self
, nadal może wydawać się dziwne, szczególnie dla programistów pochodzących z innych języków, że self
jest to jawnie przekazywane jako parametr za każdym razem, gdy definiujemy metodę. Jak mówi The Zen of Python: „ Wyraźne jest lepsze niż ukryte ”.
Więc dlaczego musimy to zrobić? Na początek weźmy prosty przykład. Mamy Point
klasę, która definiuje metodę distance
obliczania odległości od począ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
Utwórzmy teraz instancję tej klasy i znajdźmy odległość.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
W powyższym przykładzie __init__()
definiuje trzy parametry, ale właśnie przekazaliśmy dwa (6 i 8). Podobnie distance()
wymaga podania jednego, ale zero argumentów. Dlaczego Python nie narzeka na niezgodność numeru tego argumentu?
Co się dzieje wewnętrznie?
Point.distance
aw p1.distance
powyższym przykładzie są różne i nie są dokładnie takie same.
>>> type(Point.distance) >>> type(p1.distance)
Widzimy, że pierwsza to funkcja, a druga to metoda. Osobliwością metod (w Pythonie) jest to, że sam obiekt jest przekazywany jako pierwszy argument do odpowiedniej funkcji.
W przypadku powyższego przykładu wywołanie metody p1.distance()
jest w rzeczywistości równoważne Point.distance(p1)
.
Generalnie, gdy wywołujemy metodę z pewnymi argumentami, wywoływana jest odpowiednia funkcja klasy poprzez umieszczenie obiektu metody przed pierwszym argumentem. Więc wszystko, co obj.meth(args)
się stanie Class.meth(obj, args)
. Proces wywołujący jest automatyczny, podczas gdy proces odbierający nie jest (jest to jawne).
Z tego powodu pierwszym parametrem funkcji w klasie musi być sam obiekt. Zapisanie tego parametru jako self
zwykłej konwencji. Nie jest to słowo kluczowe i nie ma specjalnego znaczenia w Pythonie. Moglibyśmy użyć innych nazw (np. this
), Ale jest to wysoce odradzane. Używanie nazw innych niż self
jest mile widziane przez większość programistów i pogarsza czytelność kodu ( liczy się czytelność ).
Ja można uniknąć
Teraz już wiesz, że sam obiekt (instancja) jest automatycznie przekazywany jako pierwszy argument. Tego niejawnego zachowania można uniknąć podczas tworzenia metody statycznej . Rozważmy następujący prosty przykład:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Tutaj @staticmethod
jest dekorator funkcji, który powoduje stat_meth()
statyczność. Utwórzmy instancję tej klasy i wywołajmy metodę.
>>> a = A() >>> a.stat_meth() Look no self was passed
Z powyższego przykładu widzimy, że niejawne zachowanie przekazywania obiektu jako pierwszego argumentu zostało uniknięte przy użyciu metody statycznej. Podsumowując, metody statyczne zachowują się jak zwykłe stare funkcje (ponieważ wszystkie obiekty klasy mają wspólne metody statyczne).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Self Is Here to Stay
Jawne self
nie jest unikalne dla Pythona. Pomysł ten został zapożyczony z Moduli-3 . Poniżej przedstawiono przypadek użycia, w którym staje się pomocny.
W Pythonie nie ma jawnej deklaracji zmiennej. Wchodzą do akcji przy pierwszym zadaniu. Użycie self
zmiennej ułatwia odróżnienie atrybutów (i metod) instancji od zmiennych lokalnych.
W pierwszym przykładzie self.x jest atrybutem instancji, podczas gdy x jest zmienną lokalną. Nie są takie same i znajdują się w różnych przestrzeniach nazw.
Wiele osób zaproponowało uczynienie siebie słowem kluczowym w Pythonie, na przykład this
w C ++ i Javie. Pozwoliłoby to wyeliminować zbędne użycie jawności self
z formalnej listy parametrów w metodach.
Chociaż ten pomysł wydaje się obiecujący, tak się nie stanie. Przynajmniej nie w najbliższej przyszłości. Głównym powodem jest kompatybilność wsteczna. Oto blog od samego twórcy Pythona wyjaśniający, dlaczego jawne ja musi pozostać.
__init __ () nie jest konstruktorem
Jednym z ważnych wniosków, jakie można wyciągnąć z dotychczasowych informacji, jest to, że __init__()
metoda nie jest konstruktorem. Wielu naiwnych programistów Pythona jest z nim mylonych, ponieważ __init__()
jest wywoływany, gdy tworzymy obiekt.
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)
Przykładowy przebieg:
>>> 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