Объекты в Python: самое простое и подробное освоение

Курс последовательно объясняет, что такое объекты в Python и как устроена объектная модель языка. Вы разберёте типы, ссылки, мутабельность, атрибуты, методы, классы и распространённые ошибки на понятных примерах.

1. Что такое объект: тип, значение и идентичность

Что такое объект: тип, значение и идентичность

Python часто описывают фразой «всё — объект». Это не красивый слоган, а практическая реальность: числа, строки, списки, функции, классы, модули — всё это объекты.

В этой статье мы разберёмся с самой базовой моделью объектов в Python: у каждого объекта есть тип, значение и идентичность. Это фундамент, на котором держится понимание переменных, присваивания, сравнения, изменяемости и многого другого.

Что в Python называют объектом

Объект — это сущность в памяти, с которой работает интерпретатор Python.

  • У объекта есть тип: он говорит, какими операциями объект поддерживает и какое поведение у него будет.
  • У объекта есть значение (содержимое): данные, которые объект хранит.
  • У объекта есть идентичность: «кто именно это?» — конкретный экземпляр в памяти.
  • Важно: переменная в Python — не «коробка с данными», а имя, которое ссылается на объект.

    Переменная — это ссылка на объект

    Посмотрим на самый простой пример:

  • Создаётся объект числа 10.
  • Имя x начинает ссылаться на этот объект.
  • Если потом сделать так:

    то не создаётся «второе число 10». Имя y начинает ссылаться на тот же объект, что и x.

    !Имена x и y ссылаются на один и тот же объект 10

    Три ключевых свойства объекта

    Тип объекта

    Тип отвечает на вопрос: «что это за объект и что с ним можно делать?».

    В Python тип можно получить функцией type():

    Обычно вы увидите что-то вроде <class 'int'>, то есть тип — int.

    Тип определяет, например:

  • какие методы есть у объекта (у строки есть .lower(), у списка есть .append())
  • как работает + (для чисел — сложение, для строк — конкатенация)
  • можно ли объект менять «на месте» (это связано с изменяемостью)
  • Полезная справка: type — Built-in Functions

    Значение объекта

    Значение — это данные, которые объект представляет.

    Примеры:

  • у объекта int значением является само число (например, 10)
  • у объекта str значением является последовательность символов (например, "hello")
  • у объекта list значением является набор элементов (например, [1, 2, 3])
  • Важно различать:

  • значение (например, число 10)
  • объект (конкретная сущность в памяти, которая представляет это значение)
  • Идентичность объекта

    Идентичность отвечает на вопрос: «это тот же самый объект или другой?».

    В Python для наблюдения идентичности есть функция id():

    Если x и y ссылаются на один объект, то id(x) и id(y) будут одинаковыми.

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

  • Это целое число, уникальное для объекта на время его жизни.
  • В CPython (самая распространённая реализация Python) id() часто совпадает с адресом в памяти, но полагаться на это как на «гарантию» не нужно.
  • Справка: id — Built-in Functions

    Сравнение: == и is — это про разное

    Новички часто путают два вида сравнения:

  • == сравнивает значения (эквивалентность).
  • is сравнивает идентичность (тот же самый объект или нет).
  • Пример со списками:

    Пример с присваиванием:

    Справка: Операторы сравнения — is / is not

    Изменяемость: почему она связана с объектами

    Один из самых практичных вопросов: «изменится ли объект, если я “поменяю переменную”?».

    Ответ зависит от того, изменяемый объект или неизменяемый.

    Неизменяемые объекты (immutable)

    Неизменяемые — это те, которые нельзя поменять «на месте». При попытке «изменить» вы на самом деле получаете новый объект.

    Типичные примеры: int, float, str, tuple.

    Пример:

    Идентичность, скорее всего, изменится, потому что создался новый объект.

    Изменяемые объекты (mutable)

    Изменяемые — это те, которые можно менять «на месте», и тогда все имена, которые ссылаются на объект, увидят изменения.

    Типичные примеры: list, dict, set.

    Пример:

    Тут изменился объект списка, а не «переменная b».

    Мини-набор правил, который уже спасает от ошибок

  • Имя (переменная) в Python — это ссылка на объект.
  • У объекта есть:
  • - тип (какие операции доступны) - значение (данные) - идентичность (конкретный объект)
  • type(x) — про тип.
  • id(x) — про идентичность (уникальность объекта на время жизни).
  • == сравнивает значения, is — идентичность.
  • У изменяемых объектов изменения видны через все ссылки.
  • Итог

    Вы узнали базовую модель: объект = тип + значение + идентичность.

    Дальше в курсе мы будем постоянно опираться на неё, чтобы без магии объяснить:

  • почему присваивание не копирует объект
  • почему is почти всегда не подходит для сравнения значений
  • почему со списками и словарями легко случайно «изменить не то»
  • как устроены копии (поверхностные и глубокие)
  • 2. Ссылки и присваивание: как переменные указывают на объекты

    Ссылки и присваивание: как переменные указывают на объекты

    В прошлой статье мы разобрали фундамент: у каждого объекта в Python есть тип, значение и идентичность. Теперь добавим недостающий элемент картины: как именно переменные связаны с объектами.

    Главная идея этой статьи: присваивание в Python не копирует объект. Оно привязывает имя (переменную) к объекту.

    Имя, ссылка и объект

    В Python переменная — это имя, которое ссылается на объект.

  • Объект живёт в памяти.
  • Имя в вашем коде указывает на этот объект.
  • Присваивание меняет то, на что указывает имя.
  • !Имена могут указывать на один объект, а затем одно имя можно перепривязать к другому объекту

    Присваивание создаёт связь, а не копию

    Рассмотрим код:

    Что произошло:

  • Создался объект числа 10.
  • Имя x привязалось к этому объекту.
  • Имя y привязалось к тому же самому объекту.
  • Проверим идентичность:

    Важно: для небольших целых чисел и некоторых строк CPython может переиспользовать объекты (кэширование). Поэтому пример с 10 может выглядеть так, будто числа «всегда один объект». Лучше наблюдать механику на изменяемых объектах, например на списках.

    Переприсваивание: имя можно перенаправить

    Когда вы делаете новое присваивание, имя начинает ссылаться на другой объект.

    Это не «изменение числа 10 на 20». Числа (тип int) неизменяемые, поэтому происходит так:

  • x ссылался на объект 10.
  • Затем x перепривязали к объекту 20.
  • Одна ссылка или две: aliasing (псевдонимы)

    Когда два имени ссылаются на один и тот же изменяемый объект, это называют псевдонимами (часто говорят алиасинг).

    Здесь изменился объект списка, поэтому изменение видно через оба имени.

    del удаляет имя, а не обязательно объект

    Оператор del убирает привязку имени.

    В этом примере объект списка не исчез, потому что на него всё ещё есть ссылка b.

    Идея такая:

  • Пока на объект есть хотя бы одна ссылка, объект «нужен» программе.
  • Когда ссылок не осталось, интерпретатор может освободить память (детали зависят от реализации Python).
  • Справка: Оператор del

    Присваивание нескольким именам

    Цепочка присваиваний

    Это означает: все три имени указывают на один и тот же список.

    Если вы хотели три независимых списка, нужно создавать их отдельно:

    Распаковка (множественное присваивание)

    Это читается так:

  • Справа создаётся кортеж значений (1, 2).
  • Слева имена a и b привязываются к соответствующим объектам.
  • Полезно знать про обмен местами без временной переменной:

    Справка: Операторы присваивания

    Составное присваивание += и изменяемость

    Операции вроде += похожи на «изменение переменной», но на самом деле поведение зависит от типа объекта.

    Пример со списком (изменяемый объект)

    В большинстве случаев для списка += изменяет объект на месте (как будто вызвали метод расширения списка), и поэтому изменения видны через все ссылки.

    Пример с кортежем (неизменяемый объект)

    Кортеж нельзя изменить «на месте», поэтому создаётся новый кортеж, и имя t перепривязывается к нему. Имя u продолжает указывать на старый объект.

    Аргументы функций: это тоже присваивание

    Когда вы вызываете функцию, параметры внутри функции становятся новыми именами, привязанными к переданным объектам.

    Здесь lst и items в момент вызова указывают на один и тот же список. Функция меняет объект списка — поэтому результат виден снаружи.

    Если внутри функции вы сделаете переприсваивание параметра, это затронет только локальное имя:

    Типичные ошибки и как их избегать

  • Ожидать, что b = a создаёт копию
  • - Для копии используйте явное копирование. - Для списков часто достаточно a.copy() или a[:]. - Для вложенных структур может понадобиться copy.deepcopy.

    Справка: Модуль copy

  • Использовать цепочку x = y = [], когда нужны независимые списки
  • - Создавайте каждый список отдельно.

  • Путать is и ==
  • - == сравнивает значения. - is проверяет, один и тот же ли это объект.

    Итог

    Теперь у нас полная базовая механика:

  • Присваивание в Python — это привязка имени к объекту, а не копирование.
  • Несколько имён могут ссылаться на один объект, особенно важно это для изменяемых типов.
  • del удаляет имя, а не обязательно объект.
  • Составные операции (+=) и аргументы функций подчиняются той же модели: имена привязываются к объектам, а изменяемость определяет, видны ли изменения через другие ссылки.
  • Дальше в курсе эта модель понадобится, чтобы уверенно разбираться с копированием, изменяемостью контейнеров и поведением объектов в более сложных ситуациях.

    3. Мутабельность и неизменяемость: списки, строки, кортежи

    Мутабельность и неизменяемость: списки, строки, кортежи

    В предыдущих статьях мы договорились о главном:

  • объект в Python имеет тип, значение и идентичность
  • переменная — это имя, которое ссылается на объект
  • присваивание не копирует объект, а привязывает имя к уже существующему объекту
  • Теперь разберём свойство, которое сильнее всего влияет на повседневное поведение кода: мутабельность (изменяемость) и неизменяемость.

    Что такое мутабельность

    Мутабельный объект можно изменить на месте (без создания нового объекта). При этом:

  • идентичность (обычно наблюдаемая через id()) остаётся той же
  • все имена, которые ссылаются на объект, увидят изменение
  • Неизменяемый объект нельзя изменить на месте. Любая операция, которая выглядит как “изменение”, на практике делает одно из двух:

  • создаёт новый объект
  • привязывает имя к этому новому объекту (переприсваивание)
  • Эта тема напрямую продолжает идею из статьи про ссылки: если несколько имён смотрят на один мутабельный объект, изменения будут общими.

    Быстрая карта типов: список, строка, кортеж

    | Тип | Пример | Мутабельность | Что это означает на практике | |---|---|---|---| | list | [1, 2, 3] | мутабельный | можно менять содержимое: добавлять, удалять, заменять элементы | | str | "hello" | неизменяемый | нельзя поменять символ “внутри строки”; только создать новую строку | | tuple | (1, 2, 3) | неизменяемый | нельзя заменить/добавить/удалить элемент; только создать новый кортеж |

    Справки по типам:

  • list — Built-in Types
  • str — Built-in Types
  • tuple — Built-in Types
  • Список (list) — мутабельный

    Изменение “на месте” и общие ссылки

    Список можно менять напрямую:

    Ключевой момент появляется, когда у одного списка несколько имён:

    Объяснение:

  • b = a не создаёт копию
  • a и b — два имени для одного объекта списка
  • append меняет объект списка на месте, поэтому результат виден через оба имени
  • !Два имени ссылаются на один список, и изменение видно через оба

    Замена элемента в списке

    У списка можно заменить элемент по индексу:

    Это тоже изменение на месте.

    += со списком обычно меняет объект

    На практике это часто эквивалентно “расширению” списка. Важно запомнить эффект: видят все ссылки.

    Строка (str) — неизменяемая

    Строку нельзя изменить “внутри”:

  • нельзя заменить символ по индексу
  • нельзя “добавить символ в конец” без создания новой строки
  • Попытка заменить символ даст ошибку:

    Конкатенация создаёт новый объект

    Что произошло:

  • старая строка "hi" осталась как объект (пока на неё есть ссылки)
  • создалась новая строка "hi!"
  • имя s перепривязалось к новой строке
  • Почему это важно

    Если вы делаете много “склеек” строк в цикле, вы создаёте много временных строк. Часто эффективнее собирать части в список и затем делать "".join(parts).

    Справка:

  • str.join — Built-in Types
  • Кортеж (tuple) — неизменяемый

    Кортеж похож на “фиксированный список”: элементы заданы и не меняются.

    Нельзя менять элементы

    += с кортежем создаёт новый кортеж

    Объяснение:

  • кортеж нельзя расширить “на месте”
  • создаётся новый кортеж
  • имя t перепривязывается
  • u остаётся ссылкой на старый кортеж
  • Важная тонкость: неизменяемый контейнер может содержать мутабельные объекты

    Кортеж неизменяемый, но он может хранить внутри, например, список (а список мутабельный).

    Почему это работает:

  • вы не меняете сам кортеж (его “ячейки” остаются теми же)
  • вы меняете объект списка, который лежит внутри кортежа
  • Отсюда практическое правило:

  • неизменяемость контейнера означает, что нельзя заменить ссылки на элементы
  • но это не запрещает изменять мутабельные объекты, на которые эти ссылки указывают
  • Как это связано с == и is

    Напоминание из первой статьи:

  • == сравнивает значения
  • is проверяет один и тот же объект или нет
  • Мутабельность делает это особенно важным:

  • два разных списка могут быть равны по ==, но это разные объекты
  • если два имени указывают на один мутабельный объект, is будет True, и изменения будут общими
  • Практические выводы: как не ловить “магические” баги

    Когда надо думать про копию

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

    Простейшие варианты для списка:

    Если структура вложенная (список внутри списка), поверхностная копия может быть недостаточной. Тогда используют глубокое копирование:

    Справка:

  • Модуль copy
  • Когда выбирать кортеж вместо списка

    Кортеж часто выбирают, когда нужно подчеркнуть “это фиксированный набор данных, его нельзя менять”. Это снижает риск случайных изменений.

    Итог

  • Мутабельные объекты (например, list) можно менять на месте, и изменения видны через все ссылки.
  • Неизменяемые объекты (например, str, tuple) нельзя менять на месте: операции создают новый объект и перепривязывают имя.
  • Неизменяемость контейнера не гарантирует “глубокую” неизменяемость содержимого: внутри кортежа может лежать мутабельный список.
  • Понимание мутабельности напрямую опирается на модель “имя → объект” из предыдущей статьи и помогает уверенно предсказывать поведение кода.
  • 4. Атрибуты и методы: доступ, вызовы, пространство имён

    Атрибуты и методы: доступ, вызовы, пространство имён

    В прошлых статьях мы закрепили модель Python: имя (переменная) ссылается на объект, а у объекта есть тип, значение и идентичность. Теперь добавим ещё один ключевой слой: у объектов есть атрибуты, и через атрибуты мы получаем данные и поведение.

    Если коротко, то:

  • атрибут — это “что-то, что принадлежит объекту” и доступно через точку
  • метод — это атрибут, который можно вызвать, чтобы выполнить действие
  • пространство имён атрибутов — это “словарь имён” (ключи) и значений, где лежат атрибуты объекта
  • Эта тема напрямую связана с предыдущими:

  • когда вы делаете obj.attr, вы не “лезете в переменную”, вы обращаетесь к объекту, на который указывает имя
  • если метод меняет мутабельный объект (например, список), изменения увидят все ссылки на этот объект
  • Что такое атрибут

    Атрибут — это значение, доступное у объекта по имени.

    Доступ к атрибуту обычно пишут через точку:

    Примеры атрибутов:

  • у строки есть метод .lower
  • у списка есть метод .append
  • у объекта вашего класса могут быть поля вроде .name и .age
  • Важно: в Python не нужно заранее объявлять атрибуты экземпляра (если класс это позволяет). Часто атрибут появляется в момент присваивания.

    Справка:

  • Attribute references
  • Что такое метод

    Метод — это атрибут, который является вызываемым объектом.

    Вызов метода выглядит так:

    Пример со списком:

    Здесь:

  • append — атрибут объекта items
  • append(...) — вызов этого атрибута как функции
  • Атрибуты как “данные” и как “поведение”

    Обычно удобно думать так:

  • атрибуты-данные хранят состояние (например, user.name)
  • атрибуты-методы описывают действия (например, items.append)
  • Но технически это всё атрибуты: разница лишь в том, что лежит по имени атрибута.

    Доступ к атрибутам: точка и функции getattr, setattr

    Доступ через точку

    Доступ по имени-строке: getattr

    Иногда имя атрибута известно только во время выполнения (например, пришло из конфигурации).

    getattr(obj, "name") возвращает атрибут name.

    Справка:

  • getattr
  • Установка атрибута: setattr

    Справка:

  • setattr
  • Проверка существования: hasattr

    Справка:

  • hasattr
  • Смотреть список доступных имён: dir

    dir полезен для исследования объекта, но это не “точный список всех атрибутов”, а скорее удобная подсказка.

    Справка:

  • dir
  • Пространство имён атрибутов: где “живут” атрибуты

    У многих объектов есть внутреннее хранилище атрибутов экземпляра: __dict__. Это обычный словарь.

    Здесь видно важную идею:

  • присваивание u.name = "Alice" добавило запись в пространство имён атрибутов экземпляра
  • Но __dict__ есть не у всех объектов. Например, у list нет привычного __dict__ для произвольных атрибутов.

    Атрибуты экземпляра и атрибуты класса

    У класса тоже есть свои атрибуты.

    Если вы присваиваете атрибут на экземпляр, он становится атрибутом экземпляра:

    Практическое правило:

  • атрибут класса общий для всех экземпляров (пока не “перекрыт” атрибутом экземпляра)
  • атрибут экземпляра принадлежит конкретному объекту
  • Справка:

  • Classes
  • Как Python ищет атрибут: порядок поиска

    Когда вы пишете obj.attr, Python делает примерно такую работу:

  • Сначала пытается найти attr в атрибутах экземпляра (часто это obj.__dict__).
  • Если не нашёл — ищет в атрибутах класса type(obj).
  • Если не нашёл — ищет в базовых классах (родителях) по цепочке наследования.
  • Если всё равно не нашёл — получает AttributeError.
  • !Порядок поиска атрибута при обращении obj.attr

    Технически это работает через специальные механизмы доступа к атрибутам, но на уровне новичка важнее предсказуемая модель поиска.

    Справка:

  • object.__getattribute__
  • Почему метод “знает”, к какому объекту он относится

    Один из самых полезных моментов: метод, взятый у экземпляра, обычно становится связанным с этим экземпляром.

    Что здесь важно понять:

  • в классе Counter inc — это функция, определённая в теле класса
  • когда вы обращаетесь c.inc, Python возвращает объект “связанный метод”, который уже “помнит” c
  • поэтому при вызове m() аргумент self подставляется автоматически
  • Если обратиться к методу через класс, связывания не будет:

    Здесь мы явно передали c как self.

    Справка:

  • Method objects
  • Атрибуты и мутабельность: типичный источник багов

    Атрибут может хранить мутабельный объект, и тогда изменяется не “атрибут как имя”, а объект, на который он ссылается.

    Это продолжение предыдущих статей:

  • b.items — ссылка на список
  • .append меняет список на месте
  • Особенно опасна ситуация, когда мутабельный объект лежит в атрибуте класса, потому что он будет общим для всех экземпляров:

    Если вы хотите отдельный список для каждого экземпляра, обычно создают его в момент создания экземпляра (например, внутри __init__, тему которого мы разберём отдельно).

    Мини-набор практических правил

  • obj.attr читает атрибут; obj.attr = value устанавливает атрибут.
  • Метод — это атрибут, который можно вызвать: obj.method().
  • Атрибуты экземпляра обычно хранятся в obj.__dict__ (но не всегда).
  • Атрибуты класса хранятся в Class.__dict__ и могут быть общими.
  • Поиск obj.attr идёт: экземпляр → класс → базовые классы → AttributeError.
  • Вызов obj.method() обычно автоматически передаёт объект как self.
  • Итог

    Вы научились смотреть на точку . как на главный инструмент объектной модели Python:

  • через атрибуты объект предоставляет данные и поведение
  • метод — это атрибут, который становится “удобным” для вызова через связывание с экземпляром
  • пространство имён атрибутов помогает объяснить, почему атрибут может быть “у всех общий” или “у каждого свой”
  • Дальше эта база пригодится при изучении конструкторов (__init__), свойств, а также более тонких правил копирования и изменения объектов в структурах данных.

    5. Интроспекция объектов: dir, type, isinstance, id, help

    Интроспекция объектов: dir, type, isinstance, id, help

    В прошлых статьях мы построили рабочую модель Python:

  • имя (переменная) ссылается на объект
  • у объекта есть тип, значение и идентичность
  • одни объекты мутабельные, другие неизменяемые
  • у объектов есть атрибуты и методы, доступные через точку
  • Теперь добавим набор инструментов, который помогает видеть эту модель вживую и уверенно разбираться с незнакомыми объектами: интроспекция.

    Интроспекция — это когда программа (и вы как разработчик) исследует объект во время выполнения: какой у него тип, какие атрибуты есть, какие методы можно вызвать, один ли это объект или другой, и где посмотреть документацию.

    !Шпаргалка по 5 основным функциям интроспекции

    Зачем нужна интроспекция

    Интроспекция особенно полезна, когда:

  • вы получили объект из чужого кода или библиотеки и не знаете, что с ним делать
  • вы видите ошибку AttributeError и хотите понять, какие атрибуты вообще есть
  • вы путаетесь между == и is, или не понимаете, меняется объект на месте или создаётся новый
  • вы хотите проверить, подходит ли объект под ожидаемый интерфейс (например, это список или “что-то похожее на последовательность”)
  • Дальше разберём по очереди dir, type, isinstance, id, help и соберём их в удобный рабочий алгоритм.

    dir: что доступно у объекта

    Функция dir(obj) возвращает список имён атрибутов, которые “видны” у объекта.

    Обычно в выводе вы увидите:

  • “обычные” методы (например, append, pop, sort)
  • “магические” атрибуты и методы (например, __class__, __len__, __iter__)
  • Как читать вывод dir

    Практический подход:

  • если ищете “как добавить элемент” — смотрите на что-то вроде append, extend, insert
  • если ищете “как получить длину” — видите __len__ и вспоминаете len(obj)
  • Пример: нашли метод — проверили, что он вызывается.

    Важное ограничение dir

    dir() — это подсказка для исследования, а не строгая гарантия.

  • для некоторых объектов dir() может показывать больше или меньше, чем вы ожидаете
  • часть атрибутов может вычисляться “на лету” при доступе
  • Официальная справка: dir — Built-in Functions

    type: какой тип у объекта

    type(obj) отвечает на вопрос: “экземпляр какого класса этот объект?”.

    Это напрямую связано с первой статьёй курса: тип — одна из трёх базовых характеристик объекта.

    Официальная справка: type — Built-in Functions

    type и поиск методов

    Тип определяет доступные методы и поведение операций.

    isinstance: принадлежит ли объект типу (или его потомку)

    Если type(obj) — это “точное имя типа”, то isinstance(obj, SomeType) — это проверка по иерархии типов:

  • вернёт True, если объект — экземпляр SomeType
  • или экземпляр класса-наследника SomeType
  • Официальная справка: isinstance — Built-in Functions

    Чем isinstance лучше, чем сравнение через type

    Сравнение type(obj) is SomeType часто слишком жёсткое.

    Пример: создадим наследника списка.

    Если вам важно “ведёт себя как список”, чаще подходит isinstance.

    isinstance с несколькими типами

    Можно передать кортеж типов, чтобы проверить “хотя бы один из”.

    id: идентичность объекта

    id(obj) возвращает число, которое уникально для объекта на время его жизни.

    Эта функция позволяет наблюдать идентичность, о которой мы говорили в первой статье.

    Официальная справка: id — Built-in Functions

    Как id помогает понять мутабельность

    У мутабельных объектов изменения обычно происходят “на месте”, и id остаётся тем же.

    У неизменяемых объектов “изменение” обычно означает создание нового объекта и перепривязку имени.

    Важная оговорка про id

  • id() уникален только пока объект жив
  • после удаления объекта интерпретатор может позже создать новый объект, и его id может совпасть со старым
  • Поэтому id() — это инструмент для отладки и понимания, а не “постоянный идентификатор навсегда”.

    help: встроенная документация

    help(obj) показывает справку: что это за объект, какие методы есть, какие у них параметры, что они делают.

    Это один из самых быстрых способов понять “как пользоваться” объектом прямо в интерактивной сессии.

    Официальная справка: help — Built-in Functions

    help для метода

    Так вы увидите описание append, ожидаемые аргументы и смысл.

    Почему help связан с атрибутами и методами

    Из прошлой статьи вы знаете, что метод — это атрибут.

  • dir(obj) помогает найти имя метода
  • help(obj.method) помогает понять, как его вызывать
  • Практический алгоритм исследования незнакомого объекта

    Когда у вас есть объект obj и непонятно, что с ним делать:

  • Узнайте тип: type(obj)
  • Посмотрите доступные имена: dir(obj)
  • Проверьте предположения о типе: isinstance(obj, ...)
  • Посмотрите справку по объекту или конкретному методу: help(obj) или help(obj.some_method)
  • Если подозреваете “общую ссылку” и неожиданные изменения, сравните идентичность: id(obj) и is
  • Мини-пример:

    Типичные ошибки

  • Путать isinstance(x, T) и type(x) is T
  • Использовать id() как “стабильный идентификатор” для хранения в файле или базе данных
  • Считать dir() “полным списком всего, что возможно”, и строить на этом жёсткую логику программы
  • Заменять документацию угадыванием: если нашли метод через dir, лучше сразу уточнить поведение через help
  • Итог

    Вы получили базовый набор интроспекции, который идеально ложится на модель курса:

  • type() показывает тип объекта
  • isinstance() проверяет принадлежность к типу с учётом наследования
  • id() помогает наблюдать идентичность и понимать, создаётся новый объект или меняется старый
  • dir() показывает “какие имена доступны” у объекта
  • help() даёт встроенную документацию по объекту и его методам
  • Эти инструменты помогут вам уверенно читать и отлаживать код, потому что вместо догадок вы сможете проверять объект прямо во время выполнения.

    6. Классы и экземпляры: создание, self, __init__, наследование

    Классы и экземпляры: создание, self, __init__, наследование

    Мы уже построили базовую модель Python:

  • имя (переменная) ссылается на объект
  • у объекта есть тип, значение, идентичность
  • у объектов есть атрибуты и методы (доступ через точку)
  • интроспекция (type, isinstance, dir, help, id) позволяет исследовать объект
  • Теперь соберём всё это в практическую картину: как создавать свои типы объектов с помощью классов, как устроены экземпляры, что такое self, зачем нужен __init__, и как работает наследование.

    Класс и экземпляр в терминах объектов

    Класс в Python — это тоже объект. Он играет роль шаблона (типа), по которому создаются новые объекты.

    Экземпляр (instance) — это конкретный объект, созданный по классу.

    Если связать с тем, что мы уже знаем:

  • class User: ... создаёт объект-класс User
  • u = User() создаёт объект-экземпляр, а имя u начинает ссылаться на него
  • у экземпляра есть атрибуты (например, u.name) и методы (например, u.say_hi())
  • !Связь класса и экземпляров: класс создаёт экземпляры, у экземпляров свои атрибуты, а методы берутся из класса

    Полезные ссылки из документации:

  • Классы в учебнике Python
  • Модель данных Python (специальные методы)
  • Как объявить класс

    Минимальный класс:

    Теперь можно создать экземпляр:

    Здесь видно связь с прошлыми темами:

  • u — имя, ссылающееся на объект
  • type(u) — тип объекта (наш класс)
  • Атрибуты экземпляра: состояние объекта

    Часто у каждого экземпляра должно быть своё состояние.

    Самый простой способ (хотя в реальном коде так делают редко) — присвоить атрибут после создания:

    Это напрямую продолжает тему атрибутов:

  • u1.name и u2.name — атрибуты двух разных объектов
  • атрибуты экземпляра обычно хранятся в u1.__dict__ и u2.__dict__
  • Метод: функция, которая живёт в классе

    Методы обычно объявляют внутри класса:

    Важно: say_hi лежит в классе, но вызывается на экземпляре.

    Что такое self

    self — это первый параметр метода экземпляра, через который метод получает доступ к конкретному объекту, на котором был вызван.

    Ключевая идея из статьи про методы:

  • когда вы пишете u.say_hi(), Python берёт функцию User.say_hi и связывает её с объектом u
  • поэтому self подставляется автоматически
  • Проверим, что self действительно указывает на тот объект, через который мы вызвали метод:

    Это связывает воедино темы id(), методов и объектной модели.

    Почему self называется self

    Это соглашение. Технически можно назвать иначе, но почти весь Python-код использует self, и так легче читать чужие программы.

    Зачем нужен __init__

    Обычно мы хотим, чтобы объект создавался сразу “готовым”: с нужными атрибутами.

    Для этого используют специальный метод __init__.

  • __init__ вызывается после создания экземпляра
  • в нём обычно задают атрибуты экземпляра
  • Пример:

    Здесь происходит следующее:

  • выражение User("Alice") создаёт новый объект-экземпляр
  • затем вызывается User.__init__(u, "Alice") (параметр self — это наш экземпляр)
  • строка self.name = name записывает атрибут в объект
  • Справка:

  • object.__init__
  • __init__ и присваивание атрибутов

    self.name = name — это не “переменная внутри объекта”, а атрибут, то есть запись в пространстве имён объекта.

    Можно думать так:

  • слева self.name — “в объекте self атрибут name”
  • справа name — обычное локальное имя (параметр)
  • Атрибуты класса и атрибуты экземпляра

    У класса тоже могут быть атрибуты:

    Перекрытие атрибута класса атрибутом экземпляра

    Это связано с порядком поиска атрибутов, который мы обсуждали ранее:

  • сначала Python ищет species в атрибутах экземпляра
  • если не находит, берёт из класса
  • Типичный баг: мутабельный атрибут класса

    Если атрибут класса — мутабельный объект, он становится общим для всех экземпляров.

    Как исправлять: делать список атрибутом экземпляра в __init__.

    Наследование: расширяем и переиспользуем поведение

    Наследование позволяет объявить новый класс на основе существующего.

    Пример: базовый класс Animal и дочерний Dog.

    Что важно:

  • Dog наследует __init__ от Animal, поэтому Dog("Rex") умеет создавать атрибут name
  • Dog переопределяет speak, поэтому поведение меняется
  • Справка:

  • Наследование в учебнике Python
  • !Наследование и поиск методов: сначала в дочернем классе, затем в родительском

    super(): как вызвать реализацию родителя

    Частая задача: расширить поведение родителя, а не заменить полностью.

    Например, у нас есть Animal.__init__, и в Dog мы хотим добавить новый атрибут breed, но при этом не переписывать установку name.

    super() возвращает объект, через который можно обратиться к методам родителя (и не только прямого родителя, если иерархия сложнее).

    Справка:

  • super — Built-in Functions
  • isinstance и issubclass: проверки для наследования

    Мы уже использовали isinstance. С наследованием его смысл особенно удобен:

    А если нужно проверить отношение классов:

    Справка:

  • isinstance — Built-in Functions
  • issubclass — Built-in Functions
  • object: общий базовый класс

    Если вы явно не указываете родителя, Python всё равно делает класс наследником object.

    Это одна из причин, почему у “любого объекта” есть общий набор базовых возможностей.

    Как применять интроспекцию к классам и экземплярам

    Так как классы и экземпляры — обычные объекты, к ним применимы инструменты из прошлой статьи:

    Практическая интерпретация:

  • type(u) показывает класс экземпляра
  • type(User) показывает, что сам класс тоже объект (его тип — type)
  • dir(User) помогает увидеть, какие атрибуты есть у класса
  • Итог

  • Класс — объект, который задаёт новый тип и хранит методы.
  • Экземпляр — объект, созданный по классу; его состояние обычно хранится в атрибутах экземпляра.
  • self — ссылка на конкретный экземпляр внутри метода.
  • __init__ — главный способ инициализировать объект при создании.
  • Атрибуты бывают класса (общие) и экземпляра (индивидуальные).
  • Наследование позволяет переиспользовать и расширять поведение; super() помогает аккуратно вызывать реализацию родителя.
  • Интроспекция (type, isinstance, dir, help) отлично работает и для классов, и для экземпляров.
  • 7. Магические методы: __str__, __repr__, сравнения и контейнеры

    Магические методы: __str__, __repr__, сравнения и контейнеры

    В предыдущих статьях мы уже видели, что:

  • всё в Python — объект
  • у объекта есть атрибуты и методы, доступные через точку
  • классы помогают создавать свои объекты, а интроспекция (dir, help, type) — изучать их поведение
  • Теперь разберём механизм, который связывает обычные операторы и встроенные функции с методами ваших объектов: магические методы (их также называют специальными методами или dunder-методами от double underscore).

    Главная идея статьи: когда вы пишете print(obj), obj1 == obj2, x in container или len(container), Python обычно не делает ничего магического — он вызывает конкретные методы вроде __str__, __eq__, __contains__, __len__.

    Официальная точка входа в тему: Модель данных Python (Data model).

    !Как выражения и встроенные функции превращаются в вызовы магических методов

    Что такое магические методы и зачем они нужны

    Магический метод — это метод со специальным именем вида __name__, который Python вызывает автоматически в ответ на:

  • операторы (+, ==, <, in)
  • встроенные функции (len, repr, str, iter)
  • протоколы языка (итерация в for, обращение по индексу, форматирование)
  • Это продолжение темы из статьи про атрибуты и методы:

  • obj.__str__ — обычный атрибут (метод) объекта
  • отличие в том, что интерпретатор знает, когда его вызывать
  • Полезная привычка: если вы хотите понять, почему выражение работает так, попробуйте найти соответствующий метод в документации или через dir(obj).

    __repr__ и __str__: как объект выглядит для разработчика и для пользователя

    В Python есть два основных текстовых представления объекта.

  • __repr__ — представление для разработчика: точное, диагностическое.
  • __str__ — представление для пользователя: красивое, читаемое.
  • repr: что делает __repr__

    Функция repr(obj) вызывает obj.__repr__() и возвращает строку.

    Типичные места, где используется __repr__:

  • интерактивная консоль Python (когда вы просто вводите имя переменной)
  • вывод элементов контейнеров (например, список показывает элементы через repr)
  • логи и отладочная печать
  • Документация: repr.

    Хорошее практическое правило:

  • __repr__ должен помогать понять, что это за объект и из чего он состоит
  • часто делают так, чтобы repr был похож на код создания объекта (когда это разумно)
  • Пример:

    str: что делает __str__

    Функция str(obj) вызывает obj.__str__().

    Документация: str.

    print(obj) почти всегда опирается на str(obj).

    Документация: print.

    Пример, где __str__ делает более дружелюбный вывод:

    Здесь важно заметить:

  • print(p) выводит красивую форму (2, 5)
  • print([p, p]) выводит что-то вроде [Point(x=2, y=5), Point(x=2, y=5)], потому что контейнеру важнее диагностическая форма
  • Что будет, если __str__ не определить

    Если __str__ не задан, Python обычно использует __repr__ как запасной вариант.

    Это удобно:

  • иногда достаточно определить только __repr__
  • а __str__ добавлять, когда нужно отдельное пользовательское представление
  • f-строки и !r

    В f-строках есть полезный суффикс !r, который принудительно использует repr.

    Сравнения: __eq__, __lt__ и друзья

    Операторы сравнения тоже завязаны на специальные методы.

    Равенство: __eq__ и __ne__

  • a == b пытается вызвать a.__eq__(b)
  • a != b пытается вызвать a.__ne__(b)
  • Документация по правилам сравнения и специальным методам: Эмуляция числовых типов и сравнения и Основные специальные имена методов.

    Практический пример: считаем точки равными, если равны координаты.

    Почему важен NotImplemented:

  • это сигнал интерпретатору: я не умею сравнивать с таким типом
  • тогда Python может попробовать обратное сравнение (если оно есть) или вернуть False
  • Документация: NotImplemented.

    Порядок: __lt__, __le__, __gt__, __ge__

  • a < b вызывает a.__lt__(b)
  • a <= b вызывает a.__le__(b)
  • a > b вызывает a.__gt__(b)
  • a >= b вызывает a.__ge__(b)
  • Если вы хотите сортировать объекты, чаще всего достаточно определить __lt__ (и, при необходимости, __eq__).

    Пример: сортировка по расстоянию до начала координат, но без вычисления корня.

    Декоратор @total_ordering помогает не писать все методы порядка вручную: он достроит остальные на основе __eq__ и одного из методов <, <=, > или >=.

    Документация: functools.total_ordering.

    Важная связка: __eq__ и __hash__

    Если объект можно использовать как ключ в dict или элемент в set, он должен быть хешируемым.

  • по умолчанию пользовательские объекты хешируются по идентичности (как будто по адресу)
  • но если вы переопределяете __eq__, Python обычно делает __hash__ = None, чтобы объект не стал случайно хешируемым с неправильной логикой
  • Почему так:

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

    Практическое правило:

  • если вы делаете объект сравнимым по значению (__eq__), подумайте, должен ли он быть ключом dict
  • если должен, обычно объект делают неизменяемым (или, как минимум, не меняют поля, влияющие на равенство и хеш)
  • Контейнеры: len, in, индексация и итерация

    Чтобы объект вел себя как контейнер (хотя бы частично), Python использует несколько связанных протоколов.

    len(container): __len__

    len(obj) вызывает obj.__len__() и ожидает целое число .

    Документация: len и __len__.

    Пример:

    bool(obj): __bool__ и запасной вариант через __len__

    Когда Python вычисляет bool(obj) или проверяет объект в if obj:, он действует так:

  • если есть __bool__, вызывает его
  • иначе, если есть __len__, то длина 0 считается False, а любая ненулевая — True
  • Документация: __bool__.

    item in container: __contains__ и запасные варианты

    Оператор in обычно пытается вызвать:

  • container.__contains__(item)
  • Если __contains__ не определён, Python может перейти к запасным стратегиям:

  • итерация по контейнеру (если контейнер поддерживает итерацию)
  • или обращение по индексам, начиная с 0 (для объектов, которые ведут себя как последовательности)
  • Документация: __contains__.

    Пример:

    Индексация: __getitem__, __setitem__, __delitem__

    Скобки тоже завязаны на магические методы.

  • obj[i] вызывает obj.__getitem__(i)
  • obj[i] = value вызывает obj.__setitem__(i, value)
  • del obj[i] вызывает obj.__delitem__(i)
  • Документация: Эмуляция контейнеров.

    Пример контейнера-обёртки:

    Заметьте связь с темой атрибутов:

  • self._items — это атрибут-данные, который хранит реальный список
  • магические методы контейнера делегируют работу этому списку
  • Итерация: __iter__

    Чтобы объект работал в for x in obj, Python пытается получить итератор.

    Чаще всего это делается через __iter__.

    Документация: __iter__.

    Пример:

    Как выбирать, какие магические методы писать

    Практический ориентир: определяйте магические методы не ради красоты, а ради понятного интерфейса.

    Частые минимальные наборы:

  • удобный вывод: __repr__ и, при необходимости, __str__
  • равенство по значению: __eq__ (и продумать __hash__)
  • контейнер: __len__, __iter__ и, если нужно, __contains__ или __getitem__
  • Как исследовать магические методы через интроспекцию

    Здесь напрямую применяются инструменты из прошлой статьи.

    Примеры того, что полезно делать в практике:

  • dir(obj) чтобы увидеть, какие методы поддержаны
  • help(SomeClass.__repr__) чтобы прочитать документацию (особенно для встроенных типов)
  • Итог

  • Магические методы — это обычные методы объектов, которые Python вызывает автоматически, когда вы используете операторы и встроенные функции.
  • __repr__ нужен для отладки и разработчика, __str__ — для человеко-читаемого вывода.
  • Сравнения (__eq__, __lt__ и другие) задают поведение ==, сортировки и порядка; при этом важно помнить про NotImplemented и связь с __hash__.
  • Контейнерное поведение строится из протоколов: __len__, __contains__, __getitem__, __iter__ и связанных методов.
  • После этой статьи выражения вроде len(x), x in y и print(x) должны читаться как понятные вызовы методов, а не как необъяснимая магия.