я в Python, Демистифицированный

Если вы какое-то время занимались программированием на 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

Интересные статьи...