Cat

Основы ООП на Python. ч. 1

В декабре мы проводили опрос, чтобы узнать, какую тему о Python наши читатели хотели бы разобрать в следующем посте рубрики “Код в мешке”. Выбор большинства пал на ООП (объектно-ориентированное программирование): так ответило 36% проголосовавших. Что ж, ваша воля для нас – закон.

Нюансы Python rusheslav 16 Январь 2024 Просмотров: 410

Python полон парадоксов. С одной стороны, код в Python совсем необязательно должен быть выполнен в методологии ООП. Создавая телеграмм-бота, например, или простенький скрипт для парсинга данных с какого-нибудь сайта, вполне естественно обходиться без ООП. С другой стороны, как вы уже, наверное, неоднократно слышали, всё в Python является объектом, то есть экземпляром какого-то класса. Это касается буквально всего: строк, целых чисел, списков, функций и т.д. Таким образом, если вы пишете код на Python, вы автоматически используете ООП. Поэтому даже если вы планируете до конца своих дней писать код, например, в структурной парадигме, изучить ООП всё равно стоит. Чем лучше вы будете его понимать, тем эффективнее вы будете работать с классами, написанными другими людьми.

 

Что такое ООП?

В учебниках или статьях вы найдёте примерно следующее определение ООП:

  • парадигма программирования, в центре которой данные и объекты, а не функции, как при процедурном подходе.
  • методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования.
  • парадигма разработки, в рамках которой все написанные программы состоят из объектов.
  • подход, при котором программа рассматривается как набор объектов, взаимодействующих друг с другом. У каждого есть свойства и поведение.

 

В центре любого определения находится понятие “объекта”, что едва ли удивит человека, который знает, как расшифровывается ООП. Где-то упоминаются классы, где-то – свойства и поведение объектов, но в целом разговор обычно строится вокруг классов (неких шаблонов) и экземпляров этих классов (объектов, которые на основании этих шаблонов создаются). Утверждается, что объектно-ориентированное мышление вообще присуще человеку: мы, мол, видим мир как совокупность объектов, которые как-то друг с другом взаимодействуют. Даже человеческие языки устроены таким образом, чтобы описывать мир в категориях объектов, которые обладают какими-то качествами и что-то делают. В некоторых случаях вспоминают даже древнегреческих философов, а именно Платона с его миром идей и миром вещей. В первом существует, например, идея кошки (класс), некий образ, по подобию которого созданы все кошки из мира вещей (объекты).

 

Всё это справедливо и всё это важно понимать, но я предлагаю начать с чего-нибудь попроще. Всё программирование, если предельно упростить, – это разговор про данные и их последовательную обработку. А ООП – это способ хранения данных и действий с ними в одном месте. Что это означает, давайте рассмотрим на примере:

 

class Student:
 
   def __init__(self, name=""):
       self.name = name
 
   def say_hello(self):
       print(f"Привет, я {self.name}!") 

 

Здесь всего один элемент информации (атрибут класса) - имя ученика, а также всего одно действие (метод) - поздороваться. Информация об ученике и его функционал содержится в классе Student. И это очень удобно по целому ряду причин:

  • Код легче писать. Нужная информация и действия хранятся в классе. Их легко снова и снова использовать, создавая экземпляры.
  • Код легче читать. Если представить, что в нашем классе больше одного метода, то и без дополнительных объяснений будет понятно, что происходит в следующем коде:

 

stan = Student(“Стэн”)
 
stan.say_hello()
stan.do_homework()
stan.sing(“Hello people”)
stan.say_bye()

 

  • Код легче обновлять. Если нам понадобится, чтобы все созданные нами студенты говорили “Добрый день” вместо “Привет”, нам достаточно изменить код в одном месте: в классе Student.
  • Код легче разрабатывать в команде. Если код распределен по классам и объектам, то над каждым из них может работать один человек, а дальше результаты работы всех можно объединять.
  • Код можно использовать в новых проектах. Если вся информация и действия компактно собраны в классе, то его можно использовать в других проектах, создавая соответствующие объекты уже в них.

 

Что такое self?

В примере выше в двух методах (то есть функциях внутри) класса Student в качестве аргумента используется объект self. Редко кто в подробностях рассказывает о том, что это за объект. Чаще говорят о том, что он позволяет делать: он даёт доступ к атрибутам (элементам информации) класса из любого места внутри самого этого класса. В нашем примере с классом Student мы в момент инициации (создания) объекта этого класса методом __init__ ввели атрибут self.name. Благодаря этому ниже – в методе say_hello, куда мы передали объект self, – мы смогли использовать этот же атрибут для того, чтобы ученик говорил приветствие и представлялся по имени.

 

Но что же такое self? Обычно говорят, что это ссылка на текущий экземпляр класса. Тот самый, который создаётся в момент инициализации. Вернёмся к нашему примеру с классом Student и воспользуемся приёмом, который знаком любому начинающему питонисту: в любой непонятной ситуации выводи всё в консоль print’ом!

 

class Student:
 
   def __init__(self, name=""):
       self.name = name
       print(self)  # <__main__.Student object at 0x1062c7790>
 
   def say_hello(self):
       print(f"Привет, я {self.name}!")
 
 
stan = Student("Стэн")
print(stan)  # <__main__.Student object at 0x1062c7790>

 

Мы добавили два print’а:

  • вывод объекта self в методе __init__ сразу после записи имени ученика в атрибут self.name,
  • вывод экземпляра класса Student сразу после его создания и записи в переменную stan.

 

Что происходит в результате? В момент инициализации объекта класса Student, который мы записываем в переменную stan, на печать выводится объект self. Из записи <__main__.Student object at 0x1062c7790> видно, что этот объект класса Student, находящийся в модуле main, хранится в ячейке памяти за номером 0x1062c7790. Дальше на печать выводится объект в переменной stan, и мы видим, что это тот же самый объект, хранящийся в той же ячейке. Именно это и означает формулировка “ссылка на текущий экземпляр класса”.

 

Что еще важно знать про self?

  • Вместо self можно использовать любое другое название для переменной текущего объекта, но выбранного имени необходимо последовательного придерживаться в коде класса:

 

class Student:
 
   def __init__(child, name=""):
       self.name = name
 
   def say_hello(child):
       print(f"Привет, я {child.name}!")

 

При этом стоит помнить, что классическим и общепринятым вариантом всё же считается self. Об этом вам будут стараться напоминать даже некоторые IDE.

 

  • Есть способ увидеть все атрибуты, записанные в self. Для этого нужно обратиться к словарю __dict__, который ассоциирован с self:

 

class Student:
 
   def __init__(self, name="", age=0):
       self.name = name
       self.age = age
       print(self.__dict__)
 
   def say_hello(self):
       print(f"Привет, я {self.name}!")
 
 
stan = Student("Стэн", 8)
 
# {'name': 'Стэн', 'age': 8}

 

Что такое __init__()?

Последний незнакомый элемент синтаксиса, который появился в нашем первом примере, - это метод __init__, известный также как конструктор класса. Если упростить, то он служит для записи передаваемых при создании объекта данных в атрибуты объекта self. Так, например, мы передавали имя ученика при создании экземпляра класса Student.

 

Может ли класс существовать без __init__? Да, прекрасно может. Каждый раз, когда создаётся новый экземпляр класса, Python ищет в классе метод __init__. Если он его находит, то метод запускается автоматически. Если нет, то никакие атрибуты в self не записываются. Именно поэтому важно не допускать ошибок при написании данного метода. Если, например, указать _init__ (с одним подчеркиванием) или __int__ (без второй “i”), передав в них аргументы, они скорее всего никогда не попадут в self, потому что автоматически метод при инициализации объекта вызван не будет. Вызвать же написанный с ошибкой метод вручную разработчик едва ли догадается.

 

Из всего указанного выше следует, что в качестве конструктора класса можно было бы использовать и созданный вручную метод. Разница будет лишь в том, что в отличие от __init__ он не будет вызван автоматически при создании экземпляра класса. Его нужно будет явным образом вызвать в коде, передав аргументы для записи в атрибуты уже в него:

 

class Student:
 
   def initialize(self, name=""):
       self.name = name
 
   def say_hello(self):
       print(f"Привет, я {self.name}!")
 
 
stan = Student()
stan.initialize("Стэн")
stan.say_hello()  # Привет, я Стэн!

 

Некоторым IDE не понравится, что запись данных в атрибут класса происходит вне __init__, да и смысла в таком решении немного, но оно, тем не менее, возможно.

 

В следующий раз мы продолжим разговор о методах классов.

    Нет комментариев

    Реклама