Якщо ви вже деякий час програмуєте на Python (об’єктно-орієнтоване програмування), то ви точно натрапили на методи, які мають self
своїм першим параметром.
Спробуємо спочатку зрозуміти, що це за повторюваний параметр self.
Що таке self у Python?
В об'єктно-орієнтованому програмуванні, коли ми визначаємо методи для класу, ми використовуємо self
як перший параметр у кожному випадку. Давайте розглянемо визначення класу під назвою 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")
У цьому випадку всі методи, включаючи __init__
, мають перший параметр як self
.
Ми знаємо, що клас - це план об’єктів. Цей план може бути використаний для створення декількох номерів об'єктів. Давайте створимо два різних об’єкти з вищевказаного класу.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
self
Ключове слово використовується для подання примірника (об'єкта) даного класу. У цьому випадку два Cat
об'єкта cat1
і cat2
мають свої власні name
і age
атрибути. Якщо не було аргументу самозабезпечення, той самий клас не міг би вмістити інформацію для обох цих об’єктів.
Однак, оскільки клас - це лише проект, self
дозволяє отримати доступ до атрибутів та методів кожного об'єкта в python. Це дозволяє кожному об’єкту мати власні атрибути та методи. Таким чином, навіть задовго до створення цих об'єктів, ми посилаємось на об'єкти як на self
визначення класу.
Чому явно визначається щоразу?
Навіть коли ми розуміємо використання self
, це все одно може здатися дивним, особливо для програмістів, що походять з інших мов, що self
передається як параметр явно кожного разу, коли ми визначаємо метод. Як іде дзен з Python , " явне краще, ніж неявне ".
Отже, навіщо нам це робити? Для початку візьмемо простий приклад. У нас є Point
клас, який визначає метод distance
обчислення відстані від початку координат.
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
Давайте зараз створимо інстанцію цього класу та знайдемо відстань.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
У наведеному вище прикладі __init__()
визначає три параметри, але ми щойно передали два (6 та 8). Подібним чином distance()
потрібно один, але було передано нуль аргументів. Чому Python не скаржиться на невідповідність цього числа аргументів?
Що відбувається внутрішньо?
Point.distance
а p1.distance
у наведеному прикладі різні і не зовсім однакові.
>>> type(Point.distance) >>> type(p1.distance)
Ми бачимо, що перша - це функція, а друга - метод. Особливістю методів (у Python) є те, що сам об’єкт передається як перший аргумент відповідній функції.
У випадку з вищенаведеним прикладом виклик методу p1.distance()
фактично еквівалентний Point.distance(p1)
.
Як правило, коли ми викликаємо метод з деякими аргументами, відповідна функція класу викликається, розміщуючи об'єкт методу перед першим аргументом. Отже, щось подібне obj.meth(args)
стає Class.meth(obj, args)
. Процес виклику відбувається автоматично, тоді як процес прийому - ні (це явно).
Ось чому першим параметром функції у класі повинен бути сам об’єкт. Написання цього параметра self
просто є умовою. Це не ключове слово і не має особливого значення в Python. Ми могли б використовувати інші імена (наприклад this
), але це вкрай не рекомендується. Використання імен, які не self
сприймаються більшістю розробників, погіршує читабельність коду ( Читаність враховується ).
Самості можна уникнути
На даний момент ви зрозуміли, що сам об'єкт (екземпляр) передається як перший аргумент автоматично. Такої неявної поведінки можна уникнути, створюючи статичний метод. Розглянемо наступний простий приклад:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Тут @staticmethod
є декоратор функцій, який робить stat_meth()
статичним. Давайте створимо екземпляр цього класу і викличемо метод.
>>> a = A() >>> a.stat_meth() Look no self was passed
З наведеного прикладу ми бачимо, що неявної поведінки передачі об’єкта як першого аргументу вдалося уникнути під час використання статичного методу. Загалом, статичні методи поводяться як звичайні старі функції (оскільки всі об’єкти класу мають спільні статичні методи).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Себе тут, щоб залишитися
Явний self
не є унікальним для Python. Ця ідея була запозичена у Modula-3 . Нижче наведено варіант використання, коли це стає корисним.
У Python немає явного оголошення змінної. Вони починають діяти з першого завдання. Використання self
полегшує розрізнення атрибутів екземпляра (і методів) від локальних змінних.
У першому прикладі self.x - це атрибут екземпляра, тоді як x - локальна змінна. Вони не однакові, і вони лежать в різних просторах імен.
Багато хто пропонував зробити самостійне ключове слово в Python, як this
у C ++ та Java. Це усуне надмірне використання явного self
із формального списку параметрів у методах.
Хоча ця ідея здається багатообіцяючою, вона не відбудеться. Принаймні не найближчим часом. Основна причина - зворотна сумісність. Ось блог від самого творця Python, який пояснює, чому явне Я повинен залишатися.
__init __ () не є конструктором
Одним із важливих висновків, який можна зробити з інформації на даний момент, є те, що __init__()
метод не є конструктором. Багато наївних програмістів Python плутаються з ним, оскільки __init__()
його викликають, коли ми створюємо об'єкт.
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)
Зразок запуску:
>>> 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