Если вы какое-то время занимались программированием на 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, один и тот же класс не мог бы хранить информацию для обоих этих объектов.
Однако, поскольку класс является всего лишь планом, он self
позволяет получить доступ к атрибутам и методам каждого объекта в Python. Это позволяет каждому объекту иметь свои собственные атрибуты и методы. Таким образом, даже задолго до создания этих объектов мы ссылаемся на объекты как self
при определении класса.
Почему каждый раз явно определяется 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. Эта идея была заимствована из Модулы-3 . Ниже приведен пример использования, когда это становится полезным.
В Python нет явного объявления переменных. Они приступают к действиям с первого задания. Использование self
позволяет легче отличать атрибуты экземпляра (и методы) от локальных переменных.
В первом примере self.x - это атрибут экземпляра, а x - локальная переменная. Они не одинаковы и лежат в разных пространствах имен.
Многие предлагали сделать self ключевым словом в 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