Классы, методы, наследование, @property, @classmethod, @staticmethod, __str__, __repr__, __new__, __init__
Python — не чисто ООП, но поддерживает все парадигмы. Классы — это просто фабрики объектов с поведением.
class Dog:
species = "Canis lupus" # атрибут класса (общий для всех экземпляров)
def __init__(self, name: str):
self.name = name # атрибут экземпляра
def bark(self):
return f"{self.name} says woof!"
dog = Dog("Rex")
print(dog.bark()) # Rex says woof!Dog.__dict__ и разделяются всеми экземплярами.dog.__dict__ и уникальны для каждого объекта.__new__, __init__, __del____new__ — создание объекта__new__ вызывается первым, создаёт и возвращает новый экземпляр. __init__ получает уже готовый объект и инициализирует его.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value # вызывается каждый раз, даже для существующего экземпляра!
a = Singleton(1)
b = Singleton(2)
print(a is b) # True — один объект
print(a.value) # 2 — __init__ перезаписал атрибут__new__ необходим при наследовании от неизменяемых типов:
class PositiveInt(int):
def __new__(cls, value):
if value <= 0:
raise ValueError("Должно быть положительным")
return super().__new__(cls, value)__del__ — деструкторВызывается, когда счётчик ссылок объекта достигает нуля (не детерминировано):
class Resource:
def __del__(self):
print("Ресурс освобождён")
# Не полагайтесь на этот метод для критических ресурсов!
# Используйте контекстные менеджеры (with).| Тип | Сигнатура | Доступ | Пример использования |
|---|---|---|---|
| Instance method | def method(self, ...) | self, cls | dog.bark() |
| Class method | @classmethod\ndef method(cls, ...) | cls (класс) | Альтернативные конструкторы |
| Static method | @staticmethod\ndef method(...) | Нет self/cls | Вспомогательные функции |
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, s):
year, month, day = map(int, s.split('-'))
return cls(year, month, day) # создаёт экземпляр подкласса при наследовании
@staticmethod
def is_valid(year, month, day):
return 1 <= month <= 12 and 1 <= day <= 31
d = Date.from_string("2025-12-25")
Date.is_valid(2025, 13, 1) # False@classmethod — идиоматичный способ создания альтернативных конструкторов (from_string, from_json, from_env).
| Метод | Когда вызывается | Назначение |
|---|---|---|
__str__ | str(obj), print(obj), f-строки | Читаемый текст для пользователя |
__repr__ | repr(obj), REPL, логи, !r в f-строках | Точное представление для разработчика |
Если __str__ не определён, Python использует __repr__. Обратное неверно.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})" # eval(repr(p)) == p
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(1, 2)
print(repr(p)) # Point(1, 2)
print(str(p)) # (1, 2)
print(f"{p!r}") # Point(1, 2) — !r вызывает __repr__
print(f"{p}") # (1, 2) — по умолчанию __str____eq__ и __hash__class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented # не False! — позволяет other.__eq__ попробовать
return self.x == other.x and self.y == other.y
# Если определить __eq__, Python автоматически устанавливает __hash__ = None!
# Объект становится нехэшируемым. Чтобы исправить — определите __hash__:
def __hash__(self):
return hash((self.x, self.y)) # хэш кортежа из полейПравило: если объекты равны (
a == b), их хэши должны совпадать (hash(a) == hash(b)).
class Stack:
def __init__(self):
self._items = []
def __len__(self): # len(stack)
return len(self._items)
def __getitem__(self, idx): # stack[0], stack[-1], stack[1:3]
return self._items[idx]
def __setitem__(self, idx, val): # stack[0] = 'x'
self._items[idx] = val
def __contains__(self, item): # item in stack — O(1) если хэш-структура
return item in self._items # без __contains__ Python использует __getitem__ в цикле
def __call__(self): # stack() — делает объект вызываемым
return self._items.pop()__enter__ / __exit__ в классахclass DatabaseConnection:
def __enter__(self):
self.conn = connect_db()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
self.conn.rollback()
else:
self.conn.commit()
self.conn.close()
return False # не подавляем исключение
with DatabaseConnection() as conn:
conn.execute("INSERT ...")@property — геттеры, сеттеры, удалителиclass Circle:
def __init__(self, radius):
self._radius = radius # соглашение: _ означает "внутренний"
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius must be non-negative")
self._radius = value
@radius.deleter
def radius(self):
del self._radius
@property
def area(self):
return 3.14159 * self._radius ** 2 # вычисляемый атрибут (нет setter)
c = Circle(5)
print(c.radius) # 5 — getter
c.radius = 10 # setter
print(c.area) # ~314.159
del c.radius # deleterclass BankAccount:
def __init__(self, balance):
self.owner = "Alice" # public — свободный доступ
self._balance = balance # protected — соглашение "не трогай снаружи"
self.__pin = "1234" # private — name mangling!
def get_balance(self):
return self._balance
# Name mangling: __pin → _BankAccount__pin
acc = BankAccount(1000)
print(acc._balance) # работает, но нарушает соглашение
# print(acc.__pin) # AttributeError!
print(acc._BankAccount__pin) # "1234" — технически доступно, но запрещено стилемName mangling (__attr → _ClassName__attr) предотвращает случайное переопределение в подклассах, но не является настоящей защитой.
super()class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # вызывает Animal.__init__
self.breed = breed
def speak(self):
return f"{self.name} says woof"class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
D().method()
# D → B → C → A (C3 линеаризация — алгоритм MRO)MRO (Method Resolution Order) — алгоритм C3 линеаризации гарантирует:
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)super() в Python 3 без аргументов работает через магию компилятора (__class__ ячейка).
isinstance() vs type()class Animal: pass
class Dog(Animal): pass
dog = Dog()
type(dog) == Dog # True — точное сравнение, без учёта наследования
isinstance(dog, Dog) # True
isinstance(dog, Animal) # True — учитывает всю иерархию
type(dog) == Animal # False!
# Предпочитайте isinstance() для полиморфизма
def process(obj):
if isinstance(obj, Animal): # работает и для Dog, и для Cat
obj.speak()issubclass()issubclass(Dog, Animal) # True
issubclass(Dog, object) # True — всё наследует от object
issubclass(int, (str, list, int)) # True — принимает кортеж типов__slots__ — экономия памятиclass Point:
__slots__ = ('x', 'y') # запрещает __dict__, разрешает только x и y
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
# p.__dict__ # AttributeError: no __dict__
# Экономия памяти: ~40-50% на большом числе объектов
import sys
class WithDict:
def __init__(self): self.x = 1; self.y = 2
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self): self.x = 1; self.y = 2
print(sys.getsizeof(WithDict())) # ~48 байт + __dict__ ~232 байт
print(sys.getsizeof(WithSlots())) # ~56 байтОграничения __slots__:
__slots__, иначе __dict__ появится'__weakref__' в слотахfrom abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
"""Площадь фигуры."""
@abstractmethod
def perimeter(self) -> float:
"""Периметр фигуры."""
def describe(self): # конкретный метод — можно переопределять, но не обязательно
return f"Площадь: {self.area():.2f}, периметр: {self.perimeter():.2f}"
class Rectangle(Shape):
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
# Shape() → TypeError: Can't instantiate abstract class Shape
# Rectangle(3, 4) → OK, оба метода реализованы__init_subclass__ — хук при наследованииclass Plugin(ABC):
_registry = {}
def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs)
if name:
Plugin._registry[name] = cls
class PluginA(Plugin, name="plugin_a"):
pass
class PluginB(Plugin, name="plugin_b"):
pass
print(Plugin._registry) # {'plugin_a': <class 'PluginA'>, 'plugin_b': <class 'PluginB'>}from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
def resize(self, factor: float) -> None: ...
class Circle: # НЕ наследует от Drawable
def draw(self): print("O")
def resize(self, factor): self.radius *= factor
def render(obj: Drawable) -> None: # принимает любой объект с draw() и resize()
obj.draw()
render(Circle()) # работает — структурная типизация (duck typing + статический анализ)Разница: ABC — номинальная типизация (нужно наследовать), Protocol — структурная (достаточно иметь нужные методы).
from dataclasses import dataclass, field
from typing import NamedTuple
# NamedTuple — иммутабельный, tuple под капотом
class PointNT(NamedTuple):
x: float
y: float
label: str = "" # значение по умолчанию
p = PointNT(1.0, 2.0)
print(p.x, p[0]) # 1.0 1.0 — доступ и по имени, и по индексу
# p.x = 3 # AttributeError — иммутабельный
# dataclass — изменяемый по умолчанию, богатые возможности
@dataclass
class PointDC:
x: float
y: float
label: str = ""
history: list = field(default_factory=list) # изменяемые дефолты — через field!
def distance_to(self, other: "PointDC") -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
p = PointDC(1.0, 2.0)
p.x = 3 # OK — изменяемый| Характеристика | NamedTuple | @dataclass |
|---|---|---|
| Изменяемость | Нет | По умолчанию да (frozen=True → нет) |
| Наследование от tuple | Да | Нет |
Автогенерация __eq__ | Да | Да |
__hash__ | Да (иммутабельный) | Только при frozen=True |
| Доступ по индексу | Да | Нет |
__slots__ | Нет (вручную) | @dataclass(slots=True) (3.10+) |
__dict__ и vars()class Person:
species = "Homo sapiens"
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
print(vars(p)) # {'name': 'Alice', 'age': 30} — только атрибуты экземпляра
print(p.__dict__) # то же самое
print(vars(Person)) # атрибуты класса, включая методы и species__new__class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance@classmethodclass Animal:
@classmethod
def create(cls, animal_type: str):
types = {"dog": Dog, "cat": Cat}
return types[animal_type]()class JSONMixin:
def to_json(self):
import json
return json.dumps(vars(self))
class User(JSONMixin):
def __init__(self, name, email):
self.name = name
self.email = email
User("Alice", "a@b.com").to_json() # '{"name": "Alice", "email": "a@b.com"}'CachedProperty, который кэширует результат вычисления свойства при первом доступе.__eq__ должен возвращать NotImplemented, а не False, при несовместимых типах?LimitedDict, который ограничивает количество пар ключ-значение (например, 100), удаляя самые старые при переполнении.super() без аргументов в Python 3?Vector с операторами +, * (скалярное произведение), len(), repr().__init_subclass__ используется для автоматической регистрации плагинов.Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.