В этом видео мы начнем знакомиться с классами и с экземплярами классов. Надеюсь, вы, так или иначе, сталкивались с объектно-ориентированным программированием, когда работали с другими языками программирования. Но, даже если нет, ничего страшного. Примеры, которые мы будем показывать, достаточно просты, чтобы вы смогли понять. На самом деле, в объектно- ориентированном программировании ничего сложного нет. По сути, это просто вопрос организации кода. Вообще, зачем нужны классы? Часто, когда говорят о классах, говорят, что их используют тогда, когда нужно отобразить реальные предметы, вещи на программный код. Отчасти это так, но я бы сказал, что классы нужны для того, чтобы объединить функционал какой-то, связанный общей идеей и смыслом, в одну сущность, причем у этой сущности может быть свое внутреннее состояние, а также методы, которые позволяют это состояние модифицировать. Про методы класса мы также с вами будем говорить. Какие можно привести реальные примеры использования классов? Например, это может быть обертка над соединением к базе данных. У нас есть класс, в котором есть состояние. И это состояние — это постоянное TCP соединение с базой. Также у этого класса могут быть методы, которые предоставляют некий интерфейс доступа к этому TCP соединению. Методы представляют собой, например, возможности выбрать какие-то данные из базы, либо положить какие-то данные в базу. Тем самым мы инкапсулируем, то есть скрываем, TCP соединение внутри класса, а пользователю нашего класса предоставляем удобный интерфейс доступа к данным. Также, например, классы хорошо ложатся на реализацию всевозможных игр. Например, давайте подумаем о игре жанра RPG (role-playing game). Там есть квесты, монстры, игроки, предметы инвентаря — все это со своими свойствами и возможностями. Классы позволяют все это реализовать в программном коде. Давайте начнем знакомиться с классами, и прежде всего, откроем интерактивный интерпретатор Python, запустив консоль, набрав Python 3, и посмотрим. Встроенные типы, о которых мы рассказывали вам на первой неделе, на самом деле являются классами в Python'е. Я набрал int, и видим, что это класс int. То же самое, например, с классом float. На второй неделе мы знакомили вас со структурами данных, такими как, например, dict — словарь, или список — list. Это тоже классы. Когда мы создаем переменную и присваиваем ей число, например, 13, мы, на самом деле, создаем объект класса int и можем с ним работать. Посмотрим тип нашей переменной — это как раз класс int. В Python есть встроенная функция isinstance. isintance — это функция, которая позволяет в рантайме посмотреть, удовлетворяет ли какой-то объект какому-либо классу, типу. Давайте попробуем вызвать эту функцию и проверим, что переменная num, которой мы присвоили число 13, является объектом класса int. Это true. Если бы мы попробовали проверить, что это float, мы увидели бы, что это false. Как мы видим, классы есть и в стандартной библиотеке Python'а. Давайте посмотрим, как реализовывать и писать свои собственные классы. Чтобы объявить класс, мы используем ключевое слово class, за которым следует название класса. Классы в Python'е принято называть CamelCase'ом, то есть с большой буквы. После этого мы ставим двоеточие и дальше идет блок класса, блок пространства имен класса. В данном случае мы используем ключевое слово pass. Помните, на первой неделе я говорил, что ключевое слово pass может использоваться для определения класса, который ничего не делает? Вот это как раз тот случай. В данном случае мы объявили класс Human, который пока ничего не умеет. Есть другой способ определить пустой класс, который ничего не делает — используя docstring. Мы пишем имя класса, и в блоке класса мы просто пишем строку документирования. Это также валидный синтаксис на Python. Теперь, если мы посмотрим, что представляет собой объект Robot, мы увидим, что это класс "__main__.Robot". main в данном случае — это имя модуля, в котором он определен, мы ничего не импортировали, никаких модулей не создавали, поэтому наш модуль — это модуль main. Дальше мы видим названия класса. Если мы посмотрим на то, какие есть методы у этого объекта, только что созданного — у класса Robot, — то мы увидим, что их на самом деле много. Про некоторые из них мы с вами поговорим, а пока давайте поговорим о том, как создавать экземпляры или, как по-другому говорят, объекты класса. Предположим, что у нас есть класс Planet — класс, который описывает какую-либо планету, но этот класс описывает абстрактную планету, то есть он описывает все планеты в целом. Когда мы работаем с планетами, нас будут зачастую интересовать именно конкретные экземпляры планет, то есть не какая-то абстрактная планета, а, например, Земля, Марс и так далее. Для того, чтобы создать экземпляр класса, мы обращаемся к имени класса как к функции — используем синтаксис круглых скобочек — и получаем объект, либо экземпляр, класса. Это мы можем посмотреть на то, что нам печатает Python, когда мы принтим только что созданную переменную, которая связана с объектом класса. И мы видим, что это _main_.Planet object, то есть это уже объект, а не просто класс, и далее идет адрес, по которому этот объект располагается в памяти. С классами можно работать точно так же, как и с другими вещами в Python, так как все в Python есть объект, как вы знаете. Ничто нам не мешает оперировать классами, как любыми другими вещами. Например, давайте посмотрим на слайд, и мы видим, что на этом слайде мы создали список, который называется "Солнечная система". Мы знаем, что в нашей Солнечной системе есть 8 планет. Давайте проитерируемся от 0 до 8 невключительно, и создадим 8 планет, и добавим эти планеты в список. Мы видим, что у нас получилось. Наш финальный список Солнечной системы содержит как раз 8 планет. Что важно отметить? Важно отметить, что экземпляры класса хешируются, то есть экземпляры классов могут быть ключами словаря. На слайде вы видите точно такой же пример, как и предыдущий, за исключением того, что теперь мы в качестве Солнечной системы используем словарь. Однако не все так хорошо. Наши планеты, которые мы объявили, то есть создали экземпляры класса Planet, они не имеют имени. Это не очень удобно, с этим сложно работать. У каждой планеты должно быть свое имя. Для того чтобы дать планете имя, мы должны переопределить инициализатор класса. В Python'е у классов существует очень много магических методов. Со многими из них мы с вами будем знакомиться на этой неделе, а также на следующих неделях. Но самый главный, по сути, магический метод — это метод _init_ с двумя подчеркиваниями в начале и в конце. Метод init позволяет переопределить инициализацию класса. Чтобы вы поняли, давайте посмотрим это на примере. Мы переопределили метод _init_. Про то, как он представлен, мы скоро с вами поговорим. Но давайте посмотрим. Мы объявляем переменную Earth, которая является экземпляром класса Planet. После этого вызывается метод _init_. Метод _init_ вызывается автоматически Python'ом при создании экземпляра. Первым аргументом метод _init_ принимает ссылку на только что созданный экземпляр класса. Он еще не проинициализирован — как раз метод _init_ его инициализирует. Также он после self принимает те аргументы, которые мы передаем, обращаясь к классу, когда мы создаем экземпляр. То есть вот в данном случае на место name подставится строка Earth. Внутри инициализатора мы можем по ссылке self установить так называемые атрибуты экземпляра. В данном случае мы ставим атрибут экземпляра name и присваиваем его аргументу name. Теперь у нас у каждой планеты есть свое имя. Мы можем обратиться к имени планеты, а точнее, к атрибуту экземпляра, используя точку, то есть имя переменной.name. И мы видим, что на экран напечатается Earth. Также, если мы сейчас посмотрим и напечатаем наш класс, то мы увидим, что на экран выведется вот такое вот описание, то есть это объект класса Planet. А что если мы хотим, чтобы когда мы печатаем нашу планету, Python печатал ее имя? На самом деле, для этого существует еще один магический метод — метод _str_ с двумя подчеркиваниями в начале и в конце. Он позволяет нам переопределить то, как будет печататься объект. В данном случае мы возвращаем имя нашей планеты и теперь, когда мы печатаем на экран наш экземпляр, используя функцию print, на экран выводится имя нашей планеты. Вернемся к примеру, где мы создаем Солнечную систему. Теперь мы населяем Солнечную систему планетами со своим именем. Однако, обратите внимание, что, несмотря на то, что мы переопределили метод _str_, магические классы, мы внутри списка видим объекты, которые печатаются по-прежнему в том представлении, которое печаталось до этого, до переопределения _str_. Дело в том, что в данном случае Python использует другой магический метод, когда мы внутри списка, то есть встроенный, другой магический метод — метод _repr_ с двумя подчеркиваниями, который мы также имеем возможность переопределить для того, чтобы и внутреннее представление объекта также печаталось правильно в данном случае. Если мы переопределим _repr_ и выполним тот же самый пример, то мы уже увидим, что внутри нашего списка планеты названы правильно. Магических методов, на самом деле, существует масса, и мы будем вас знакомить с другими магическими методами. Например, есть метод _add_ с двумя подчеркиваниями, который позволяет переопределить, как ведут себя два объекта класса, когда мы их складываем друг с другом, и так далее. Но многие из них мы даже не сможем осветить в рамках нашего курса, но вы всегда сможете посмотреть это в документации. Теперь давайте вернемся к атрибутам экземпляра и посмотрим, как с ними можно работать. Когда мы создаем экземпляр класса Planet, мы можем обратиться к атрибуту через точку, как я уже показывал. Однако это не все — мы можем в любой момент изменить атрибут экземпляра, присвоив ему другое значение. Например, как здесь на слайде показано, мы присвоили атрибуту экземпляра name другое значение Second Earth, и оно меняется. Если мы обратимся к несуществующему атрибуту экземпляра, то вызовется Exception AttributeError. Про то, как отлавливать такие ошибки, мы с вами будем говорить на этой неделе. Также, если мы удалим какой-нибудь атрибут экземпляра, используя конструкцию del — мы можем это сделать, — то, обратившись потом к mars.name, мы также получим AttributeError. Давайте подведем итог. Мы с вами посмотрели, как объявлять классы, объявлять классы, которые ничего по большому счету не делают, то есть это просто объявления в пространстве имен класса, который следует после объявления. Мы научились создавать экземпляры классов, или по-другому, объекты классов. Мы рассмотрели, как инициализировать экземпляр класса, то есть как наделять его конкретными атрибутами, чтобы как-то делать наши объекты уникальными — например, как мы помним, дать уникальное имя каждой из планет. Также мы научились работать с атрибутами экземпляра классов, то есть мы можем создавать их, читать и удалять.