12. Классы и объекты¶
12.1. Объектно-ориентированное программирование¶
Python является объектно-ориентированным языком программирования, что означает наличие в языке средств объектно-ориентированного программирования (ООП).
Объектно-ориентированное программирование возникло в 1960 годы, но только в середине 1980-х оно стало основной парадигмой программирования, используемой при создании новых программ. ООП было разработано, чтобы справиться с быстро растущими размерами и сложностью программных систем, и упростить последующие сопровождение и модификацию этих больших и сложных систем.
До сих пор мы писали программы с использованием парадигмы процедурного программирования. Процедурное программирование фокусируется на создании функций или процедур, которые работают с данными. Объектно-ориентированное программирование фокусируется на создании объектов, которые содержат и данные и функциональность.
12.2. Определяемые пользователем типы данных¶
Класс, в сущности, определяет новый тип данных. Мы уже некоторое время пользуемся встроенными типами данных Python, а теперь готовы создать наш собственный (пользовательский) тип.
Рассмотрим понятие математической точки. В пространстве двух измерений, точка — это два числа (координаты), с которыми работают как с одним объектом. В математике координаты точки часто записываются в скобках, разделенные запятой. Например, (0, 0) представляет начало координат, а (x, y) представляет точку, расположенную на x единиц правее и на y единиц выше, чем начало координат.
Естественный способ представления точки на языке Python — с помощью двух чисел. Но остается вопрос: как именно объединить эти два числа в один составной объект? Очевидное и быстрое решение состоит в том, чтобы использовать список или кортеж, и в некоторых случаях оно будет наилучшим.
Альтернативой является определение нового типа, называемого также классом. Этот подход требует немного больше усилий, но имеет преимущества, которые вскоре станут вам понятны.
Определение нашего класса Point (англ.: точка) выглядит так:
Определения классов могут встречаться в программе где угодно, но обычно их помещают в начале, после предложений import . Синтаксические правила для определения класса такие же, как и для других составных предложений. Первая строка — заголовок, начинающийся с ключевого слова class , за которым следуют имя класса и двоеточие, следующие строки — тело класса.
Приведенное выше определение создает новый класс Point . Предложение pass ничего не делает; мы воспользовались им потому, что тело составного предложения не может быть пустым.
Для этой цели подойдет и документирующая строка:
Создав класс Point , мы создали новый тип Point . Представители этого типа называются экземплярами или объектами этого типа. Создание экземпляра класса выполняется с помощью вызова класса. Классы, как и функции, можно вызывать, и мы создаем объект типа Point , вызывая класс Point :
Переменная p содержит ссылку на новый объект типа Point .
Можно думать о классе, как о фабрике по изготовлению объектов. Тогда наш класс Point — фабрика по изготовлению точек. Сам класс не является точкой, но содержит все, что необходимо для производства точек.
12.3. Атрибуты¶
Как и объекты реального мира, экземпляры классов обладают свойствами и поведением. Свойства определяются элементами-данными, которые содержит объект.
Можно добавить новые элементы-данные к экземпляру класса с помощью точечной нотации:
Этот синтаксис подобен синтаксису для обращения к переменной или функции модуля, например, math.pi или string.uppercase . И модули, и экземпляры класса создают свое собственное пространство имен, и синтаксис для доступа к элементам тех и других — атрибутам — один и тот же. В данном случае атрибуты, к которым мы обращаемся, — элементы-данные в экземпляре класса.
Следующая диаграмма состояний показывает результат выполненных присваиваний:

Переменная p ссылается на объект класса Point , который содержит два атрибута. Каждый из атрибутов ссылается на число.
Тот же самый синтаксис используется для получения значений атрибутов:
Выражение p.x означает: возьмите объект, на который указывает переменная p , затем возьмите значение атрибута x этого объекта. В приведенном примере мы присваиваем полученное значение переменной с именем x . Переменная x и атрибут x не вступают в конфликт имен, поскольку принадлежат разным пространствам имен.
Точечную нотацию можно использовать как часть любого выражения, так что следующие предложения совершенно типичны:
Первая строка выводит (3, 4) . Вторая строка вычисляет значение 25.
12.4. Инициализирующий метод и self ¶
Поскольку наш класс Point предназначен для представления математических точек в двумерном пространстве, все экземпляры этого класса должны иметь атрибуты x и y . Но пока это не так для наших объектов Point .
Для решения этой проблемы добавим в наш класс инициализирующий метод.
Метод ведет себя как функция, но является частью объекта. Доступ к методу, как и доступ к атрибутам-данным, осуществляется при помощи точечной нотации. Инициализирующий метод вызывается автоматически, когда вызывается класс.
Чтобы получше разобраться, как работают методы, давайте добавим еще один метод, distance_from_origin (англ.: расстояние от начала):
Создадим несколько экземпляров точек, посмотрим на их атрибуты, и вызовем наш новый метод для этих объектов:
В определении метода первый параметр всегда указывает на экземпляр класса. Традиционно этому параметру дают имя self. В только что рассмотренном примере параметр self последовательно указывает на объекты p , q , и r .
12.5. Объекты как параметры¶
Объект можно передать в качестве параметра, как любое другое значение. Например:
Функция print_point принимает объект Point в качестве аргумента и выводит его значение. Если выполнить print_point(p) с объектом p , определенным выше, то функция выведет (3, 4) .
12.6. Равенство объектов¶
Смысл слова ‘равенство’ кажется совершенно ясным. Но если говорить об объектах, то мы скоро обнаружим неоднозначность этого слова.
Например, что означает утверждение, что значения двух переменных типа Point равны? Что соответствующие объекты Point содержат одинаковые данные (координаты точки)? Или что обе переменные указывают на один и тот же объект?
Чтобы выяснить, ссылаются ли две переменные на один и тот же объект, используется оператор == . Например:
Хотя p1 и p2 содержат одинаковые координаты, они являются разными объектами. Но если присвоить переменной p1 значение p2 , то две переменных будут альтернативными именами одного и того же объекта:
Этот тип равенства называется поверхностным равенством, потому что он сравнивает только ссылки, а не содержимое объектов.
Для того, чтобы сравнить содержимое объектов — проверить глубокое равенство — можно написать функцию, подобную этой:
Теперь, если создать два разных объекта, содержащих одинаковые данные, с помощью same_point можно выяснить, представляют ли они одну и ту же математическую точку.
А если две переменные ссылаются на один и тот же объект, для них выполняется как поверхностное, так и глубокое равенство.
12.7. Прямоугольники¶
Пусть нам нужен класс для представления прямоугольников. Вопрос в том, какую информацию необходимо указать, чтобы описать прямоугольник? Для простоты предположим, что стороны прямоугольника ориентированы горизонтально и вертикально.
Есть несколько вариантов. Мы могли бы указать координаты центра прямоугольника и его размер (ширину и высоту). Или указать координаты одного из углов и размер прямоугольника. Или указать координаты двух противоположных углов. Традиционный способ таков: указать левый верхний угол прямоугольника и его размер.
Определим новый класс Rectangle (англ.: прямоугольник):
И создадим экземпляр этого класса:
Этот код создает новый объект Rectangle с двумя атрибутами — числами с плавающей точкой – width (англ.: ширина) и height (англ.: высота). А для того, чтобы указать левый верхний угол, можно вставить объект внутрь объекта!
Операторы точка можно сочетать, как видно из этого примера. Выражение box.corner.x означает: возьмите объект, на который указывает box , получите его атрибут corner ; затем возьмите объект, на который указывает этот атрибут, и получите атрибут x этого последнего объекта.
Следующий рисунок иллюстрирует, что у нас получилось:

12.8. Объекты как возвращаемые значения¶
Функции могут возвращать объекты. Например, функция find_center берет Rectangle в качестве аргумента и возвращает Point с координатами центра прямоугольника:
Следующий код демонстрирует использование функции:
12.9. Объекты изменяемы¶
Состояние объекта изменяется путем присваивания значений его атрибутам. Например, чтобы изменить размер прямоугольника без изменения его местоположения, изменим значения width и height :
Обобщим этот код, определив функцию grow_rect (англ.: увеличить прямоугольник):
12.10. Копирование¶
Альтернативные имена могут сделать программу трудночитаемой, так как изменения, сделанные в одном месте, могут возыметь неожиданное действие в другом. Сложно отслеживать все переменные, ссылающиеся на некоторый объект.
Вместо использования альтернативных имен для одного и того же объекта, во многих случаях полезно получить копию объекта. Модуль copy содержит функцию copy , которая способна скопировать любой объект:
После импортирования модуля copy , с помощью функции copy мы создаем новый объект класса Point . Объекты p1 и p2 являются разными объектами, но содержат одинаковые данные.
Для копирования простых объектов вроде Point , которые не содержат вложенных объектов, функции copy достаточно. Такое копирование называется поверхностным копированием.
Для объектов, подобных объектам Rectangle , которые содержат ссылку на объект Point , функция copy не совсем то, что обычно требуется. Она скопирует ссылку на объект Point , так, что и старый объект Rectangle , и новый, будут ссылаться на один и тот же объект Point .
Если мы создадим прямоугольник b1 и сделаем его копию b2 с помощью copy , то результат будет таким:

Это, скорее всего, не то, что мы хотели получить. В этом случае вызов функции grow_rect с одним объектом Rectangle не повлияет на другой, однако, вызов move_rect (см. упражнения в конце главы) с любым из прямоугольников отразится на обоих! Такое поведение сбивает с толку и чревато ошибками.
К счастью, модуль copy содержит метод deepcopy , который копирует не только сам объект, но и все вложенные объекты. Неудивительно, что эта операция называется глубоким копированием.
Теперь b1 и b2 — совершенно разные объекты.
Используя deepcopy , можно переписать grow_rect так, чтобы вместо изменения существующего объекта Rectangle , он создавал новый объект Rectangle с таким же расположением левого верхнего угла, но с другими размерами:
12.11. Time¶
В качестве еще одного примера определенного пользователем типа, создадим класс Time (англ.: время) для хранения времени дня. Определение класса будет таким:
Теперь мы можем создать новый объект класса Time и установить значения атрибутов для часов, минут и секунд:
В следующих разделах мы напишем две версии функции add_time (англ.: сложить время) для вычисления суммы двух объектов Time . Они еще раз продемонстрируют нам два типа функций, с которыми мы познакомились в главе 8: чистые и модифицирующие.
12.12. Снова чистые функции¶
Вот черновая версия функции add_time :
Функция создает новый объект Time , инициализирует его атрибуты, и возвращает ссылку на него. Это — чистая функция, поскольку она не изменяет ни один из переданных ей объектов и не имеет побочных эффектов, вроде вывода значения на печать или получения ввода от пользователя.
Вот пример использования этой функции. Мы создадим два объекта Time : current_time , содержащий текущее время, и bread_time , содержащий количество времени, необходимое хлебопечке для приготовления хлеба. Затем воспользуемся функцией add_time чтобы узнать, во сколько хлеб будет готов.
Определим функцию print_time для вывода объекта Time , воспользовавшись оператором форматирования сток:
Теперь выведем полученный нами результат:
Программа выводит 12:49:30 , и это правильный результат. Однако, в некоторых случаях результат работы функции add_time будет неверным. Можете сами привести пример такого случая?
Проблема с функцией add_time в том, что функция не учитывает случаи, когда сумма секунд или минут превышает 60. Когда это случается, необходимо выполнить перенос из переполнившегося разряда в разряд минут или часов.
Вот вторая, улучшенная, версия нашей функции:
Хотя эта версия более корректна, функция перестала быть компактной. Чуть позже будет предложен другой подход, который даст нам более короткий код.
12.13. Снова модифицирующие функции¶
Бывают случаи, когда изменение функцией объектов, переданных ей как параметры, оказывается полезным. Обычно вызывающий код сохраняет ссылки на объекты, которые он передает функции в качестве параметров, так что все изменения, сделанные функцией, доступны в вызывающем коде. Как вы помните, функции, работающие таким образом, называются модифицирующими.
Функцию increment , добавляющую указанное число секунд к объекту Time , наиболее естественно написать как модифицирующую. Вот ее черновая версия:
Первая строка выполняет основную операцию. Остальной код обрабатывает специальные случаи, которые мы обсудили выше.
Корректна ли эта функция? Что случится, если количество секунд, переданное функции, намного больше, чем 60? В этом случае недостаточно одного переноса 1 в разряд минут; мы должны выполнять переносы до тех пор, пока значение seconds продолжает быть меньше 60. Одно из возможных решений — заменить предложение if предложением while :
Теперь функция работает правильно, но это не самое эффективное решение.
12.14. Прототипирование и разработка дизайна программы¶
В этой книге мы широко используем подход к разработке программ, называемый прототипированием. Согласно этому подходу, вначале пишется грубый черновой вариант кода, или прототип, который выполняет основную работу. Затем прототип тестируется при различных условиях, и по результатам тестирования делаются доработки и устраняются найденные недостатки.
Хотя этот подход в целом эффективен, но, если пренебречь тщательным обдумыванием решаемой задачи, он может привести к излишне усложненному и ненадежному коду. Усложненному – поскольку придется иметь дело со многими специальными случаями. И ненадежному — поскольку нельзя утверждать, что все такие случаи учтены и все ошибки найдены.
Разработка дизайна программы предполагает тщательный анализ поставленной задачи и принятие ключевых решений относительно того, как именно написать программу. Предварительная разработка дизайна программы делает последующее программирование намного проще.
В данном случае, анализ подскажет нам, что объект Time , представляющий количество времени, есть не что иное, как трехразрядное число с основанием 60! Действительно, секунды — это младший разряд единиц, минуты — разряд “шестидесяток”, а часы представлены самым старшим разрядом. “Единица” старшего разряда соответствует 3600 секундам.
Когда мы писали функции add_time и increment , мы на самом деле выполняли сложение в системе счисления с основанием 60, вот почему нам пришлось делать переносы из одного разряда в другой.
Это наблюдение предлагает другой подход к задаче в целом: мы можем преобразовать три компонента объекта Time в одно единственное число, и далее выполнять арифметические действия с этим числом. Следующая функция преобразует объект Time в целое число:
Все, что нам нужно теперь, — это способ преобразовать целое число обратно в Time :
Посмотрите внимательно на приведенный код, чтобы убедиться, что преобразование выполняется корректно. Если вы согласны с этим, то перепишите add_time с использованием этих функций:
Эта версия гораздо короче первоначальной, и проверить ее корректность гораздо проще (исходя из предположения, что вызываемые ей функции сами по себе корректны).
12.15. Когда сложнее значит проще¶
Преобразование представления чисел из системы счисления с одним основанием в другую, и затем обратно, на первый взгляд кажется сложнее, чем прямое манипулирование привычными нам тремя компонентами времени: часами, минутами и секундами. А раз так, то не лучше ли полагаться на привычку, когда имеем дело со временем?
Но если мы нашли решение, основанное на представлении количества времени числом с основанием 60, и написали функции преобразования ( convert_to_seconds и make_time ), мы получаем более короткую и более надежную программу, которую легче читать и отлаживать.
Кроме того, к такой программе легче добавлять новые возможности. Представьте, например, что нам потребуется делать вычитание объектов Time , чтобы найти интервал времени между ними. Наивный подход состоит в том, чтобы реализовать вычитание с заемом из старших разрядов. Используя же функции преобразования, можно решить эту задачу гораздо проще, и с большей вероятностью получить корректный результат.
Таким образом, иногда, делая решение более сложным, или более общим, мы делаем его использование и дальнейшее развитие более простым (!) и надежным. Потому что становится меньше специальных случаев и меньше возможностей для ошибок.
Для чего в python нужен self?
Ребят, кто может нормально объяснить для чего в языке python нужен self? Уже несколько статей перечитала и все никак не могу понять. Буду крайне благодарна.
Нормально объяснить может dark:
dark: у котов внутри есть мурчалка
dark: она реализована для всех котов в классе кот
dark: в объекте кот надо как то вызвать метод мурчало у класса кот
dark: как ты это сделаешь?
dark: кот.мурчало()
dark: ежели ты вызовешь кот.мурчало(), муркнут сразу все коты на свете
dark: а ежели ты вызовешь self.мурчало(), муркнет только тот кот, на которого указывает self
![]()
Просто сам объект, от которого вызван метод.
![]()
self нужен для обращения к полям экземпляра класса и вообще для обозначения экземпляра класса. Есть подробный ответ, например, здесь .
Т.е. если приглядеться, то можно увидеть некую параллель между записью «self.name» (см. п. 1.) и записью «nick_name.name» (см. п. 4.).
Сам «self» – это своеобразная ссылка на будущий экземпляр класса «nick_name».
Чтобы понять, ответьте на вопросы
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.3.11.43304
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Знакомство с объектно-ориентированным программированием в Python: классы и экземпляры
Новичок в ООП? Узнай, как создавать классы и экземпляры в Python
Про объектно-ориентированное программирование в Python писали многие. Большинство специалистов по обработке данных, в том числе и я, сталкиваются с ситуациями, когда приходится писать функциональный код –часто в небольших скриптах или прототипах. Я работаю в этой сфере уже 3 года (а до этого еще пару лет была аналитиком данных), и до сих пор мне не хватало практического опыта в ООП.
Многие специалисты могут работать годами без необходимости писать код по принципам ООП.
Есть даже весомые доводы против реализации принципов ООП, которые в основном касаются высоких затрат на обслуживание кода и возрастающей сложностью в работе.
Но по мере развития отрасли специалисты по обработке данных сталкиваются с необходимостью написания кода промышленного качества и повышения взаимодействия с инженерными командами. Вот здесь и пригодятся базовые знания ООП и умение создавать классы и методы.
Если вы хотите поучаствовать в проектах в области науки о данных с открытым кодом, то, скорее всего, вам придется работать с объектами. Тогда и понадобятся знания ООП!
Шаг 1: создаем свой первый класс в Python
Основная идея ООП заключается в том, что у нас есть некие объекты, которые выполняют отдельные и простые задачи, а вместе они составляют сложную компьютерную программу.
Изучение ООП начинается с понимания классов. Вот общепринятое определение: классы — это «заготовки для создания объектов». Класс — это логическая структуризация данных и методов (методы равносильны функциям).
Зачастую классы основываются на объектах, которые окружают нас в деловой среде: клиенты, товары, сотрудники.
В сфере финансовых услуг мы храним и работаем с информацией о клиентах. Поэтому давайте создадим простой класс под названием «customer»:
В ООП у нас есть ключевое слово class (специалисты знакомы с функциональным эквивалентом этого слова — def).
def используется для определения функции (или метода, если функция находится внутри класса). По аналогии class используется для определения класса.
При определении класса customer мы не создаем в нем никаких клиентов, а делаем заготовку для создания клиентов-объектов.
Первое, что вы заметите внутри класса customer — это метод __init__. Метод __init__ является особым методом для классов в Python. Он вызывается каждый раз при создании класса.
__init__ — это конструктор класса.
Каждый раз при создании нового класса вы вызываете метод __init__ и используете аргумент self.
Переменная self представляет собой экземпляр самого объекта. Некоторые ОО-языки передают эту переменную в качестве скрытого параметра для определенных методов, однако Python требует объявления self.
Параметр self представляет экземпляр объекта.
Как только мы определили метод __init__ и указали параметр self, начинаем перечислять остальные параметры/аргументы, которые используются в методе __init__. В нашем примере это first, last, mobile и monthly.
Так мы создали наш первый класс customer. Теперь добавим экземпляры этого класса.
Шаг 2: экземпляры и методы
В объектно-ориентированном программировании экземпляр — это конкретное представление какого-либо объекта. Объект — это некое собирательное понятие, а экземпляром считается отдельный объект, созданный в памяти. Как правило, свойствам экземпляра присваиваются определенные значения, которые будут отличать его от других экземпляров этого типа объекта.
Создание экземпляра
В коде ниже мы создаем экземпляр под названием cust1. Вы будете создавать такой экземпляр каждый раз при сохранении информации о новом клиенте.
- cust1 — экземпляр;
- customer — класс.
Создание метода
Методы — это то же самое, что и функции, с той лишь разницей, что они находятся внутри класса. Давайте создадим простой метод annual внутри класса customer из нашего примера. Он будет брать ежемесячную зарплату определенного клиента и высчитывать его годовой доход.
Каждый метод внутри класса автоматически принимает экземпляр в качестве первого аргумента. Это означает, что вы передаете аргумент self в скобках. Если прописать self без скобок, то при его вызове появится ошибка.
Чтобы посмотреть, что происходит с методом annual, напишем следующее:
Полезный совет: для вызова атрибута в Python (например, fullname в классе customer) напишите следующее:
При вызове метода (а не атрибута) он заключается в скобки. См. выше, как это делалось с annual.
Вы создали новый класс customer, экземпляр cust1 и метод annual. Это первые практические блоки по написанию ООП-кода на Python!
Если вы хотите продолжить изучение ООП, то почитайте также про четыре принципа: инкапсуляцию, абстракцию, наследование и полиморфизм.
Что такое self в Python?

dog: у котов внутри есть мурчалка
dog: она реализована для всех котов в классе кот
dog: в объекте кот надо как то вызвать метод мурчало у класса кот
dog: как ты это сделаешь?
dog: кот.мурчало()
dog: ежели ты вызовешь кот.мурчало(), муркнут сразу все коты на свете
dog: а ежели ты вызовешь self.мурчало(), муркнет только тот кот, на которого указывает self
- Вконтакте


- Вконтакте

self — это ни в коем случае не зарезервированное слово. Это просто название переменной.
В методах класса первый параметр функции по соглашению именуют self, и это ссылка на сам объект этого класса. Но это именно соглашение. Вы вольны называть параметры как угодно.
Зачем это нужно?
Ну вот есть объект и вы хотите создать/изменить поле у этого объекта из метода этого самого объекта.
- Вконтакте

- Вконтакте
Здесь создается своя переменная blood в каждом объекте, который создан по шаблону класса Human.
Иначе получишь только одну глобальную переменную для всех и вся:
Поэтому важно поучить строготипизированные языки, чтобы понимать что реально происходит, кто кому принадлежит.
- Вконтакте
На сколько я понимаю, self — это, своего рода, ссылка на объект. Объект — это экземпляр класса (пример ниже).
Грубо говоря, мы сделали Matroskin-а — главным героем нашего класса. Записали в его паспорт имя «Матроскин». И потом, он обратился к своему навыку muur, чтобы помурлыкать)
- Вконтакте
Я думаю, для понимания self нужно обратиться к static.
Я далее буду называть поля класса просто переменными класса.
В языке Object Pascal приписка self приписывается автоматически (неявно) к переменным класса. То есть её ставить не надо.
Можно рассматривать класс, как табличку с инструкцией для создания объектов и придания им свойств. Если перед переменой (полем) класса ничего не стоит, значит перед ней неявно присутствует self (в Object Pascal). Такая переменная НЕ меняется в классе (табличке с инструкцией), а присваивается конкретному объекту. То есть такая self переменная в классе (табличке с инструкцией) не изменяется. Чтобы переменная изменилась и в самом классе, нужно перед переменной поставить static. И она будет меняться уже и в самом классе (на той самой пресловутой табличке с инструкцией). Static используют, например, для подсчёта количества созданных объектов. Для этого к static переменной при создании объекта попросту добавляют 1 (единичку). Через конструктор, например. Каждый новый объект будет в результате своего создания добавлять к static переменной ещё одну единичку.
Но это в Object Pascal.
А в Python всё почти что наоборот. Все переменные (поля) класса изначально статичные static (неявно). А чтобы переменную сделать динамической (переменной объекта) то есть self, её просто нужно задать в каком-нибудь методе класса в аргументах. Метод _init_, мне кажется, подойдёт. И назвать, так чтобы она отражала свою сущность – ‘self’ или ‘my_object ‘ (отсылка, что переменная изменяется в объекте, а не в самом классе)