Объектно-ориентированное программирование в Python
Если ООП для вас новое или вы учите Python с нуля, сначала пройдите материалы без привязки к синтаксису: парадигмы и уровни абстракции, затем ООП — о разделе — зачем объекты, введение, абстракция, инкапсуляция, наследование, полиморфизм.
Ниже — как это устроено в Python.
Теория и синтаксис Python
| Понятие ООП | Как выражено в Python |
|---|---|
| АДТ, класс | class; класс — объект метакласса type |
| Инкапсуляция | объединение в классе; сокрытие через _ и __ (договорённость) |
| Наследование | class Child(Parent):, MRO при множественном наследовании |
| Полиморфизм подтипов | duck typing, abc.ABC, переопределение методов |
| Ad hoc-полиморфизм | перегрузка через @overload (typing) |
| Параметрический полиморфизм | list[T], TypeVar (аннотации 3.9+) |
| Сообщения | вызов obj.method(); близко к модели Smalltalk |
Определения без привязки к языку — раздел 4-08-oop. Синтаксис и типы Python — о разделе Python.
Кратко для новичка:
- Класс — шаблон с полями и методами; объект (экземпляр) — конкретный представитель класса с собственными значениями полей.
- Метод — функция внутри класса; первый аргумент
self— ссылка на вызывающий объект. - Наследование — дочерний класс переиспользует и расширяет родительский (
class Knight(Warrior)). - Полиморфизм — один вызов (
animal.speak()), разное поведение уDogиCat.
ООП в Python
Три отличия Python от классического ООП в Java или C#:
- Всё — объект. Числа, функции, модули и сами классы — объекты; класс — экземпляр метакласса
type. Классы можно создавать динамически. - Сокрытие — по договорённости. Префикс
_nameсигнализирует внутреннее поле;__nameвключает name mangling (_Class__name), но технической блокировки нет. - Duck typing. Совместимость по поведению: если у объекта есть нужные методы, он подходит. Явные интерфейсы необязательны (модуль
abc— для строгих контрактов).
Что нужно знать:
Интерактивная схема — класс и объект (псевдокод, подходит для любого ООП-языка). Полный разбор принципов: ООП в разделе "Код и разработка".
КЛАСС Кот
поля: имя, возраст
метод мяукнуть()
КОНЕЦ
объект barsik := новый Кот(имя="Барсик", возраст=3)
barsik.мяукнуть()
Play ITЗагрузка интерактивного демо…
Код ITЗагрузка примера кода…
Разбор примера:
countобъявлен на уровне класса и общий для всех экземпляровWarrior.- В
__init__создаются атрибуты экземпляра (self.name) и увеличивается общий счётчик. @propertyпозволяет читатьshoutкак поле, хотя это вычисляемый метод.@classmethodработает с классом черезcls,@staticmethodне использует ниself, ниcls.Knight(Warrior)наследует поведение и переопределяетshout.
Главные магические методы (dunder — double underscore):
| Метод | Когда срабатывает |
|---|---|
__new__ / __init__ | Создание и инициализация экземпляра |
__str__ / __repr__ | print(obj), str(obj) / repr(obj) |
__call__ | Вызов объекта как функции: obj() |
__len__, __getitem__, __iter__ | len(obj), obj[i], for x in obj |
__eq__, __add__ | obj1 == obj2, obj1 + obj2 |
Подробная шпаргалка на 20 частых методов — в статье Магические методы и дандер-методы.
class Point:
def __init__(self, x, y): # __new__ уже создал объект; здесь — атрибуты
self.x, self.y = x, y
def __repr__(self): # repr(point), вывод в REPL
return f"Point({self.x}, {self.y})"
def __add__(self, other): # point1 + point2
return Point(self.x + other.x, self.y + other.y)
def __len__(self): # len(point)
return int((self.x**2 + self.y**2)**0.5)
Разбор примера:
__init__задаёт состояние после того, как__new__вернул экземпляр.__repr__задаёт удобное строковое представление объекта для отладки.__add__определяет поведение оператора+для двух объектовPoint.__len__подключает поддержку встроенной функцииlen(point).- Магические методы интегрируют пользовательский класс в стандартные операции Python.
Инкапсуляция — по договорённости, типы — по поведению, классы — по желанию можно менять в рантайме.
Пример класса в Python
Код ITЗагрузка примера кода…
Разбор примера:
__init__задаёт стартовые характеристики каждого нового юнита.@property damageпересчитывает урон при каждом обращении на основе текущих статов.attack(self, target)принимает другой объект и уменьшает егоhealth.- Внизу создаются два экземпляра
Unitс разными параметрами, затем они "атакуют" друг друга.
Ключевое слово class создаёт новый класс. Имя класса начинается с заглавной буквы по соглашению об именовании в Python.
Метод __init__ выполняется автоматически при создании экземпляра класса. В этом методе устанавливаются начальные значения атрибутов объекта через обращение к self.
Каждый атрибут объекта хранит отдельное значение. Префикс self указывает, что атрибут принадлежит конкретному экземпляру класса.
Декоратор @property превращает метод в вычисляемое свойство. При каждом обращении к damage происходит пересчёт значения на основе текущих характеристик объекта. Такой подход обеспечивает актуальность данных без явного вызова метода.
Метод attack принимает другой объект в качестве аргумента. Первый параметр self ссылается на объект, у которого вызывается метод. Внутри метода изменяется состояние целевого объекта через прямое обращение к его атрибутам.
Функция Unit() создаёт новый экземпляр класса. После создания объекта можно изменять значения его атрибутов через точечную нотацию.
Объекты взаимодействуют через вызов методов друг друга. При атаке одного объекта другим происходит изменение внутреннего состояния атакуемого объекта, что демонстрирует принцип инкапсуляции и взаимодействия через публичный интерфейс.
Объектно-ориентированное программирование
Объектно-ориентированное программирование — это парадигма программирования, в основе которой лежит концепция объектов, представляющих собой экземпляры классов. Каждый объект инкапсулирует состояние (данные) и поведение (методы), что позволяет моделировать сущности предметной области с высокой степенью абстракции. ООП опирается на четыре ключевые принципа — инкапсуляцию, наследование, полиморфизм и абстракцию.
В Python ООП опирается на динамическую модель: тип объекта известен в рантайме, границы между типами, классами и экземплярами размыты. Это даёт выразительность и требует понимания устройства языка — duck typing, метаклассы, единообразие типов, функции как объекты первого класса.
Сравнение с C# или Java полезно как ориентир, но механизмы другие. Подробнее о парадигмах — 4-07-paradigmy, о принципах ООП — 4-08-oop.
Всё является объектом
В Python всё является объектом — числа, строки, функции, модули, классы. Каждый объект имеет тип, значение и набор атрибутов. Даже сам класс — это объект, экземпляр метакласса (по умолчанию type).
Это означает, что классы можно создавать динамически, передавать как аргументы, присваивать переменным.
class MyClass:
pass
print(type(MyClass)) # <class 'type'>
Код ITЗагрузка примера кода…
Каждый объект имеет атрибут __class__, ссылающийся на его тип, и атрибут __dict__, содержащий пространство имён (для объектов, поддерживающих его). Классы могут иметь собственные атрибуты и методы, так как они тоже объекты.
Динамичность и изменяемость
Классы и объекты в Python являются изменяемыми во время выполнения. Можно добавлять, удалять или изменять атрибуты и методы "на лету".
Это даёт большую гибкость, но усложняет статический анализ кода.
obj = MyClass()
obj.new_attr = "dynamic"
MyClass.new_method = lambda self: print("Added dynamically")
Отсутствие строгой инкапсуляции
Python не поддерживает настоящих приватных членов. Сокрытие строится на соглашениях (_, __) и name mangling. Инкапсуляция здесь — договорённость команды, без гарантии на уровне компилятора. См. инкапсуляцию в разделе ООП.
Утиная типизация
Duck typing вместо строгой типизации.
Python не требует явного наследования от интерфейса. Если объект ведёт себя как нужный тип (имеет нужные методы и поведение), он считается совместимым. Это центральный элемент полиморфизма в Python.
Если это ходит как утка и крякает как утка, значит, это утка.
Утиная типизация — подход, при котором совместимость объектов определяется наличием необходимых методов и атрибутов, а не принадлежностью к конкретному классу или интерфейсу. Название происходит от поговорки: если существо ходит как утка и крякает как утка, его можно считать уткой.
Код ITЗагрузка примера кода…
Разбор примера:
- Функция
make_it_quack_and_walkне проверяет конкретный класс объекта. - Ей важно только наличие методов
quack()иwalk(). - Поэтому с одной функцией совместимы
Duck,RobotDuckиHuman. - Это и есть практический duck typing в Python.
Функция make_it_quack_and_walk работает с любым объектом, имеющим методы quack и walk, независимо от его класса. Это устраняет необходимость в явных интерфейсах и позволяет создавать гибкие, расширяемые системы. Утиная типизация лежит в основе многих паттернов проектирования в Python, включая адаптеры и стратегии.
Метаклассы
Классы в Python создаются с помощью метаклассов. По умолчанию используется type, но можно определить собственный метакласс для изменения способа создания классов.
Это мощный механизм, позволяющий внедрять поведение на уровне определения класса (например, регистрация классов, автоматическое добавление методов).
Метакласс — это класс, экземплярами которого являются другие классы. Обычный класс создаёт экземпляры объектов, а метакласс создаёт сами классы. По умолчанию все классы в Python являются экземплярами метакласса type.
Код ITЗагрузка примера кода…
Разбор примера:
Metaнаследуется отtype, значит это метакласс.- В
__new__можно изменять пространство имён создаваемого класса до его финального создания. - В примере метакласс добавляет атрибут
created_by_meta = True. class MyClass(metaclass=Meta)создаёт класс через кастомный метакласс, а не через обычныйtype.
Метаклассы применяются для автоматической регистрации классов, применения ограничений к структуре класса, внедрения методов или атрибутов на этапе определения класса. Распространённый пример — фреймворк Django, где метаклассы обрабатывают объявления полей модели и строят схему базы данных.
Код ITЗагрузка примера кода…
Класс
Класс — это шаблон (чертеж), описывающий структуру и поведение объектов. Он определяет поля (атрибуты), методы и правила их взаимодействия. В Python класс объявляется с помощью ключевого слова class.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
Класс является вызываемым объектом. Его вызов приводит к созданию нового экземпляра.
Класс объявляется ключевым словом class, за которым следует имя класса в формате CamelCase и двоеточие. Тело класса записывается с отступом.
class SimpleClass:
pass
Класс может наследовать один или несколько базовых классов, перечисленных в круглых скобках после имени.
class Base:
pass
class Derived(Base):
pass
class MultiDerived(Base1, Base2, Base3):
pass
В теле класса определяются атрибуты класса, методы и другие члены. Атрибуты класса создаются присваиванием на уровне класса. Методы определяются как обычные функции с первым параметром self (для методов экземпляра).
class Person:
species = "Homo sapiens" # атрибут класса
def __init__(self, name, age):
self.name = name # атрибут экземпляра
self.age = age
def introduce(self):
return f"Я {self.name}, мне {self.age} лет"
Имя класса становится переменной в текущем пространстве имён, ссылающейся на объект класса. Это позволяет передавать классы как аргументы, присваивать их переменным и создавать их динамически.
Объект
Объект (экземпляр) — это конкретный экземпляр класса, имеющий собственное состояние (значения атрибутов) и доступ к поведению, определённому в классе.
p = Point(1, 2)
Состояние объекта хранится в его пространстве имён — словаре __dict__. Каждый объект имеет ссылку на свой класс через атрибут __class__.
Экземпляр создаётся вызовом класса как функции. Этот вызов приводит к выполнению метода __new__, создающего объект, и метода __init__, инициализирующего его состояние.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
self.mileage = 0
my_car = Car("Toyota", "Camry")
Вызов класса — это операция __call__, определённая в метаклассе. Для обычных классов этот вызов преобразуется в последовательность __new__ → __init__.
Фабричные функции и методы класса предоставляют альтернативные способы создания экземпляров с разной логикой инициализации.
Код ITЗагрузка примера кода…
Конструкторы
Конструктор в Python состоит из двух этапов: создание объекта методом __new__ и его инициализация методом __init__. Метод __new__ отвечает за выделение памяти и возврат нового экземпляра. Он вызывается первым и принимает класс как первый аргумент cls.
class Point:
def __new__(cls, x, y):
print(f"Создаём экземпляр класса {cls.__name__}")
instance = super().__new__(cls)
return instance
def __init__(self, x, y):
print(f"Инициализируем экземпляр с координатами ({x}, {y})")
self.x = x
self.y = y
p = Point(3, 4)
В большинстве случаев достаточно переопределять только __init__, так как базовая реализация __new__ уже корректно создаёт экземпляр. Метод __new__ требуется при создании неизменяемых типов или при необходимости вернуть объект другого класса.
Альтернативные конструкторы реализуются через методы класса с декоратором @classmethod. Это позволяет создавать объекты разными способами, сохраняя чистоту основного __init__.
Код ITЗагрузка примера кода…
self в Python
Параметр self — это ссылка на конкретный экземпляр класса, от которого вызывается метод. В отличие от некоторых языков, где ключевое слово this подставляется автоматически, Python требует явного указания первого параметра в определении метода.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rect = Rectangle(5, 10)
print(rect.area()) # Вызов преобразуется в Rectangle.area(rect)
При вызове rect.area() интерпретатор автоматически передаёт rect в качестве первого аргумента методу area. Имя self — соглашение, а не ключевое слово. Можно использовать любое другое имя, но это нарушает стандарты сообщества и снижает читаемость кода.
class Example:
def show(instance):
print(f"Экземпляр: {instance}")
e = Example()
e.show() # Работает, но так писать не принято
Параметр self даёт методу доступ к атрибутам и другим методам того же экземпляра через точечную нотацию.
Обращение к атрибутам и методам
Атрибуты и методы доступны через точечную нотацию: объект.атрибут или объект.метод(). Поиск атрибута происходит сначала в пространстве имён экземпляра (__dict__), затем в классе и далее по цепочке наследования согласно MRO.
Код ITЗагрузка примера кода…
Динамический доступ к атрибутам осуществляется функциями getattr, setattr, hasattr и delattr.
class Config:
debug = True
timeout = 30
cfg = Config()
print(getattr(cfg, 'debug')) # True
setattr(cfg, 'timeout', 60)
print(hasattr(cfg, 'log_level')) # False
delattr(cfg, 'debug')
Свойства (@property) позволяют контролировать доступ к атрибутам через методы, сохраняя простой синтаксис обращения.
Код ITЗагрузка примера кода…
Метод
Метод — это функция, определённая внутри класса и предназначенная для работы с экземпляром (или самим классом).
В Python различают несколько видов методов:
- Обычные методы (instance methods). Принимают
selfв качестве первого аргумента — ссылку на экземпляр. Вызываются от объекта.
def move(self, dx, dy):
self.x += dx
self.y += dy
- Методы класса (@classmethod). Принимают cls — ссылку на сам класс. Полезны для альтернативных конструкторов.
@classmethod
def origin(cls):
return cls(0, 0)
- Статические методы (@staticmethod). Не принимают ни self, ни cls. Логически связаны с классом, но не зависят от его состояния. По сути — обычные функции, принадлежащие пространству имён класса.
@staticmethod
def distance(p1, p2):
return ((p1.x - p2.x)**2 + (p1.y - p2.y)**2)**0.5
Поля, свойства и атрибуты
Поля (атрибуты экземпляра и класса).
Атрибуты — это данные, связанные с объектом или классом.
- Атрибуты экземпляра создаются в
__init__или позже и хранятся вinstance.__dict__. - Атрибуты класса определяются на уровне класса и разделяются между всеми экземплярами (осторожно с изменяемыми типами!).
class Counter:
count = 0 # атрибут класса
def __init__(self):
Counter.count += 1 # общая статистика
self.id = Counter.count # атрибут экземпляра
Свойства (property) позволяют контролировать доступ к атрибутам через геттеры, сеттеры и делитеры, сохраняя синтаксис обращения к обычному атрибуту.
Код ITЗагрузка примера кода…
Использование:
c = Circle(5)
print(c.area) # вызов без ()
c.radius = 10 # проверка через сеттер
Свойства особенно важны при необходимости валидации, ленивых вычислений или совместимости с существующим API.
Принципы ООП
Инкапсуляция
Интерактивная схема — инкапсуляция (псевдокод). Подробнее: Инкапсуляция.
Play ITЗагрузка интерактивного демо…
Инкапсуляция — принцип сокрытия внутренней реализации объекта. В Python для этого используют соглашения об именах и name mangling, без ключевых слов private / protected.
Одно подчёркивание (_attr) — указывает, что атрибут является внутренним. Это сигнал другим разработчикам: "не используйте напрямую, поведение может измениться".
Двойное подчёркивание (__attr) — активирует name mangling: имя атрибута преобразуется в _ClassName__attr. Это предотвращает случайное переопределение в подклассах.
Код ITЗагрузка примера кода…
Таким образом, __ — защита от случайного доступа и конфликтов имён в иерархии наследования.
Наследование
Интерактивная схема — наследование (псевдокод). Подробнее: Наследование.
Play ITЗагрузка интерактивного демо…
Наследование позволяет одному классу (производному) наследовать атрибуты и методы другого (базового). Это средство повторного использования кода и построения иерархий типов.
class Animal:
def speak(self):
raise NotImplementedError
class Dog(Animal):
def speak(self):
return "Woof!"
Python поддерживает множественное наследование. Порядок разрешения методов определяется алгоритмом C3 linearization, доступным через __mro__ (Method Resolution Order).
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__) # D -> B -> C -> A -> object
Ключевая проблема множественного наследования — алмазное наследование. Решается корректным использованием super(), который следует MRO.
Код ITЗагрузка примера кода…
Полиморфизм
Интерактивная схема — полиморфизм (псевдокод). Подробнее: Полиморфизм.
Play ITЗагрузка интерактивного демо…
Полиморфизм — способность объектов с одинаковым интерфейсом вести себя по-разному.
- Переопределение методов. Производный класс может переопределять методы базового, обеспечивая специфическую реализацию.
class Shape:
def area(self):
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
- Duck typing. В Python полиморфизм основан на наличии нужного интерфейса.
def print_area(shape):
print(shape.area()) # работает с любым объектом, у которого есть area()
Это позволяет использовать композицию вместо наследования и достигать большей гибкости.
Переопределение (override) — предоставление новой реализации метода в подклассе. Базовый метод остаётся доступным через super().
Код ITЗагрузка примера кода…
Python не поддерживает перегрузку методов (overload) в классическом смысле — нескольких методов с одинаковым именем, но разными параметрами. Последнее определение метода заменяет предыдущие.
class Calculator:
def add(self, a, b):
return a + b
def add(self, a, b, c): # Первая версия add перезаписана
return a + b + c
calc = Calculator()
print(calc.add(1, 2, 3)) # 6
# print(calc.add(1, 2)) # TypeError — add() missing 1 required positional argument
Вместо перегрузки используются аргументы по умолчанию, *args и **kwargs или модуль typing.overload для статической проверки типов без изменения поведения во время выполнения.
Код ITЗагрузка примера кода…
Декоратор @overload влияет только на анализ типов в инструментах вроде mypy и не создаёт нескольких реализаций метода.
Абстракция
Интерактивная схема — абстракция (псевдокод). Подробнее: Абстракция.
Play ITЗагрузка интерактивного демо…
Абстракция — выделение ключевых характеристик, скрывая детали реализации. В Python абстрактные классы реализуются через модуль abc.
Класс становится абстрактным, если содержит хотя бы один абстрактный метод, помеченный декоратором @abstractmethod.
Код ITЗагрузка примера кода…
ABC позволяют:
- Формально определить интерфейс.
- Запретить создание неполных реализаций.
- Использовать isinstance() для проверки соответствия интерфейсу.
Также можно определять абстрактные свойства и абстрактные классовые методы.
Модуль abc предоставляет инфраструктуру для создания абстрактных базовых классов. Абстрактный класс не может быть инстанцирован напрямую и требует реализации всех абстрактных методов в подклассах.
Код ITЗагрузка примера кода…
Абстрактными могут быть методы, свойства и классовые методы.
Код ITЗагрузка примера кода…
Абстрактные базовые классы позволяют определять интерфейсы с обязательными контрактами, обеспечивая корректность реализаций на этапе разработки. Они особенно полезны при проектировании фреймворков и библиотек, где требуется строгая спецификация поведения подклассов.
Абстрактный класс и интерфейс
В Python нет ключевого слова interface. Роль контракта выполняют два механизма:
abc.ABCи@abstractmethod— абстрактный базовый класс, как в Java; наследник обязан реализовать помеченные методы.typing.Protocol— описание "у объекта должны быть такие методы"; класс может не наследоватьProtocol, но статический анализатор (mypy, pyright) всё равно примет его, если методы совпадают. Это близко к утиной типизации с явной записью контракта.
Общая теория — Абстракция в ООП.
| Критерий | Protocol | abc.ABC |
|---|---|---|
| Вопрос при проектировании | Объект умеет нужное поведение | Тип является частью иерархии |
| Наследование | Необязательно писать (Protocol) у класса | Явное наследование от ABC |
| Поля и общий код | Только сигнатуры в протоколе | Поля, __init__, общие методы в базе |
| Проверка типов | mypy / pyright по структуре | isinstance для зарегистрированных наследников |
| Когда удобнее | Роль для несвязанных классов | Общее состояние у родственных типов |
from abc import ABC
from typing import Protocol
class WiFiConnectable(Protocol):
def connect_to_network(self) -> None: ...
class HouseholdAppliance(ABC):
def __init__(self, serial: str) -> None:
self.serial = serial
class WashingMachine(HouseholdAppliance):
def connect_to_network(self) -> None:
print(f"{self.serial}: Wi-Fi подключён")
class SmartBulb:
def connect_to_network(self) -> None:
print("Лампочка в сети")
WashingMachineнаследуетHouseholdApplianceи хранитserial.SmartBulbне наследует ABC, но подходит подWiFiConnectable, потому что есть методconnect_to_network.
См. модуль abc, typing.Protocol.
Прочие особенности
Магические методы
Магические методы (dunder, от double underscore) — методы с именами вида __xxx__, которые определяют поведение объекта при взаимодействии с языковыми конструкциями. Их вызывает интерпретатор в ответ на print, len, операторы, циклы for и т.д., а не код приложения напрямую.
| Группа | Методы | Типичный синтаксис |
|---|---|---|
| Создание | __new__, __init__ | MyClass() |
| Строка и тип | __str__, __repr__, __int__, __bool__ | print(obj), int(obj), if obj: |
| Вызов | __call__ | obj() |
| Контейнер | __len__, __getitem__, __setitem__, __delitem__, __contains__, __iter__ | len(obj), obj[k], x in obj, for x in obj |
| Сравнение | __eq__, __ne__, __gt__, … | ==, !=, > |
| Арифметика | __add__, __mul__, __abs__, __neg__, __invert__ | +, *, abs(), -obj, ~obj |
| Контекст | __enter__, __exit__ | with obj: |
Конструктор в Python — __new__ (создаёт экземпляр); __init__ только инициализирует уже созданный объект. Для __repr__ цель — однозначно описать объект в логах; правило eval(repr(obj)) == obj в продакшене не применяют через eval — это риск выполнения произвольного кода.
Полный справочник, отражённые операторы (__radd__) и типичные ошибки — Магические методы и дандер-методы.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
Магические методы — основа протоколов Python:
- итерируемый;
- контейнер;
- вызываемый;
- численный;
- т.д.
Рассмотрим реализацию упрощённого словаря с возможностью отслеживания изменений.
Код ITЗагрузка примера кода…
Использование:
td = TrackedDict(a=1)
td['b'] = 2
del td['a']
print(td.get_changes()) # [('add', 'b', 2), ('delete', 'a', 1)]
Здесь мы реализовали протокол изменяемого отображения, используя магические методы и наследование от MutableMapping, которое также предоставляет стандартные методы (keys, values, items и т.д.).
Справочник — термины, инстанцирование, доступность и сравнение
Консолидированный блок для быстрой ориентации в ООП Python. Дополняет разделы выше; подробнее о дандер-методах — Магические методы.
Глоссарий — термин Python и понятие ООП
| Термин Python | Понятие ООП | Кратко |
|---|---|---|
class | Класс (АДТ) | Вызываемый объект; экземпляр метакласса type |
экземпляр / self | Объект | Конкретный представитель класса |
__init__ | Инициализатор (не конструктор!) | Заполняет атрибуты после __new__ |
__new__ | Фабрика экземпляра | Создаёт объект; редко переопределяют |
атрибут (self.x) | Поле / состояние | Хранится в __dict__ экземпляра |
| атрибут класса | Статическое поле | Общий для всех экземпляров (осторожно с list!) |
def method(self) | Метод экземпляра | Первый аргумент — ссылка на объект |
@classmethod | Метод класса | Первый аргумент cls; альтернативные конструкторы |
@staticmethod | Утилита в пространстве класса | Без self/cls |
@property | Свойство / инкапсуляция | Геттер/сеттер с синтаксисом атрибута |
_name / __name | Сокрытие (договорённость) | внутренний / name mangling |
наследование class B(A) | Наследование | MRO при множественном наследовании |
super() | Доступ к базовому классу | Следует MRO, не только прямому родителю |
abc.ABC / @abstractmethod | Абстрактный класс | Запрет инстанцирования без реализации |
metaclass=Meta | Метакласс | Класс, создающий классы |
| duck typing | Полиморфизм по поведению | умеет метод — подходит |
dunder (__str__, __eq__) | Протоколы / операторы | Интеграция с синтаксисом языка |
Создание экземпляра и запись в переменные
В Python имя переменной — это ссылка на объект. Присваивание не копирует объект (для неизменяемых типов новая переменная указывает на то же значение в памяти).
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2) # вызов класса → __new__ → __init__
q = p # две ссылки на один объект
q.x = 10 # p.x тоже 10
import copy
r = copy.copy(p) # поверхностная копия
s = copy.deepcopy(p) # глубокая (если есть вложенные изменяемые объекты)
Цепочка при Point(1, 2):
Point.__call__(у метаклассаtype) вызываетPoint.__new__(Point, 1, 2).__new__возвращает пустой экземплярPoint.__init__(self, 1, 2)инициализирует атрибуты.- Ссылка записывается в
p.
Альтернативные конструкторы:
class User:
def __init__(self, name: str, email: str):
self.name, self.email = name, email
@classmethod
def from_dict(cls, data: dict):
return cls(data["name"], data["email"])
u = User.from_dict({"name": "Алиса", "email": "a@example.com"})
Фабрики и dataclass: @dataclass генерирует __init__; NamedTuple и TypedDict — лёгкие структуры данных без полноценного ООП.
Полиморфные ссылки: тип аннотации не ограничивает рантайм; важно поведение:
def feed(animal): # duck typing: нужен eat()
animal.eat()
feed(Dog()) # OK
feed(RobotPet()) # OK, если есть eat()
Изменяемые аргументы по умолчанию — классическая ловушка при создании объектов:
class Bad:
def __init__(self, items=[]): # ОДИН список на все экземпляры!
self.items = items
Правильно: def __init__(self, items=None): self.items = items or [].
Доступность методов и полей (сводная таблица)
В Python нет настоящих private/protected — только соглашения и name mangling.
| Обозначение | Кто видит | ООП-смысл | Примечание |
|---|---|---|---|
name | все | публичный атрибут | Стандарт для API |
_name | все (формально) | внутренний | Соглашение: не трогать снаружи |
__name | класс + mangling | псевдо-приватный | Превращается в _ClassName__name |
@property | через геттер/сеттер | инкапсуляция | Валидация без смены синтаксиса |
@property; @x.deleter | контролируемое удаление | инкапсуляция | Редко, но возможно |
метод без _ | все | публичный метод | Часть контракта |
_helper() | все (формально) | внутренний метод | Не в __all__ |
@classmethod | через класс | фабрика / class state | cls.attr |
@staticmethod | через класс | утилита | Логически рядом с типом |
__slots__ | ограничение атрибутов | экономия памяти | Фиксированный набор полей |
MRO и доступ к базовым членам:
| Механизм | Назначение |
|---|---|
super().method() | Вызов следующего в MRO |
Base.method(self) | Явный вызов конкретного класса |
getattr(obj, "x", default) | Динамический доступ |
__getattribute__ / __setattr__ | Перехват любого доступа к атрибутам |
Типичные ошибки в Python-ООП
| Ошибка | Почему плохо | Что делать |
|---|---|---|
| Мутабельный аргумент по умолчанию | Общее состояние между экземплярами | None + создание в __init__ |
| Изменяемый атрибут класса как счётчик | Все экземпляры делят один list/dict | Атрибут экземпляра или @classmethod |
Путаница __init__ и __new__ | Неверная инициализация неизменяемых типов | Для str, tuple — логика в __new__ |
Игнорирование MRO при super() | Пропуск классов в ромбе | super() без аргументов, понимать __mro__ |
Псевдоприватность через __ | Обходится через _Class__name | Договорённости + @property |
| Переопределение метода с другой сигнатурой | Ломает LSP и вызывающий код | Сохранять контракт или *args/**kwargs |
| Класс-бог (God class) | Смешение ответственностей | Декомпозиция, композиция |
@overload из typing для рантайма | Не создаёт перегрузку | *args, union-типы, singledispatch |
Забыли __eq__ без __hash__ | Объект не hashable после переопределения __eq__ | Определить оба или __hash__ = None |
Мини-сравнение с другими языками
| Аспект | Python | C# | Java | C++ |
|---|---|---|---|---|
| Всё — объект? | да | почти (struct — value) | почти (примитивы) | нет |
| Строгая приватность | нет (соглашения) | да (private) | да | да |
| Множественное наследование | да (MRO) | нет (классов) | нет (классов) | да |
| Интерфейс | abc, протоколы | interface | interface | чисто виртуальный класс |
| Полиморфизм | duck typing | virtual/override | virtual по умолчанию | virtual |
| Свойства | @property | get/set | геттеры/сеттеры | методы |
| Статика | @staticmethod | static | static | static |
| Перегрузка методов | нет (последний wins) | да | да | да |
| Метаклассы | type, кастомные | нет | нет | шаблоны (другая идея) |
Учебные примеры ООП
Небольшие самодостаточные программы, которые показывают классы, объекты, инкапсуляцию, наследование и взаимодействие нескольких типов на одной предметной области.
Класс и объект
Чертёж класса Figure и конкретные объекты — круг и квадрат.
Код ITЗагрузка примера кода…
Банковский счёт
Инкапсуляция: скрытое поле баланса и методы deposit/withdraw.
Код ITЗагрузка примера кода…
Наследование
Родитель Animal и дочерние Cat и Dog с общим eat() и своим speak().
Код ITЗагрузка примера кода…
Смартфон
Состояние объекта: заряд батареи, звонки и подзарядка.
Код ITЗагрузка примера кода…
Студент
Список оценок, средний балл и проходной порог.
Код ITЗагрузка примера кода…
Корзина покупок
Взаимодействие Product, Cart и Order при оформлении заказа.
Код ITЗагрузка примера кода…
Автомобиль
Пробег, расход топлива и напоминание о техобслуживании.
Код ITЗагрузка примера кода…
Пользователь
Скрытый пароль, вход в систему и публикация сообщений.
Код ITЗагрузка примера кода…