Классы Python от базовых конструкций до продвинутых техник
Классы в Python: базовые конструкции и альтернативы
Как определить класс и использовать наследование в Python?
Основной способ определения класса это использование ключевого слова class. Внутри класса определяются методы, первый параметр которых self, ссылающийся на экземпляр. Конструктор __init__ инициализирует атрибуты. Наследование позволяет расширять функциональность, а полиморфизм работает с разными классами через единый интерфейс.
class Animal:
def __init__(self, name):
self.name = name
self._age = 0
def speak(self):
raise NotImplementedError("Должен быть переопределён")
class Dog(Animal):
def speak(self):
return f"{self.name} говорит Гав"
class Cat(Animal):
def speak(self):
return f"{self.name} говорит Мяу"
def make_sound(animal):
print(animal.speak())
dog = Dog("Бобик")
cat = Cat("Мурка")
make_sound(dog)
make_sound(cat)
атрибуты класса python (атрибуты классов и объектов в python)
В примере self передается неявно, наследование задается скобками, а полиморфизм реализован через переопределение метода speak. Для абстрактного поведения базовый класс выбрасывает исключение.
Как уменьшить шаблонный код в классах, хранящих данные?
Модуль dataclasses предоставляет декоратор @dataclass, который автоматически генерирует __init__, __repr__, __eq__ и другие методы на основе аннотаций полей.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
z: float = 0.0
p = Point(1.0, 2.0)
print(p)
библиотека классов python (библиотека классов в python)
Пояснение: аннотации обязательны. Можно задать неизменяемость через frozen=True, управлять порядком полей с помощью field, а для изменяемых типов (список, словарь) использовать default_factory.
Как добавить вычисляемые атрибуты с контролем доступа?
Декоратор @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("Радиус должен быть положительным")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
c = Circle(5)
c.radius = 10
print(c.area)
метод объекта python (методы объектов в python)
Свойства удобны для валидации, вычисляемых полей и придания интерфейсу единообразия.
Как создавать методы, не привязанные к экземпляру или классу?
@staticmethod определяет метод, не получающий ни self, ни cls. Он ведет себя как обычная функция, помещенная в пространство имен класса. @classmethod получает cls и может вызываться как от экземпляра, так и от класса, часто используется для альтернативных конструкторов.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@classmethod
def create_from_string(cls, s):
import ast
return cls(*ast.literal_eval(s))
class Vector(MathUtils):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v = Vector.create_from_string("(3,4)")
print(v, MathUtils.add(2,3))
Python структура объекта (структура объекта в python)
Статические методы для вспомогательных функций, методы класса для фабрик.
Как гарантировать, что подклассы реализуют определенные методы?
Модуль abc предоставляет ABC (Abstract Base Class) и декоратор @abstractmethod. Класс, наследующий от ABC и содержащий abstractmethod, нельзя инстанциировать, если не переопределены все абстрактные методы.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
def perimeter(self):
return 4 * self.side
Python создание объектов (создание объектов в python)
Теперь Square можно создать, а Shape нет.
Как уменьшить потребление памяти экземплярами класса?
Атрибут класса __slots__ определяет фиксированный набор атрибутов, которые может иметь экземпляр. При этом у экземпляра не создается словарь __dict__, что экономит память и ускоряет доступ к атрибутам.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeError
Используется в приложениях, где создаются миллионы однотипных объектов.
Расширенные примеры работы с классами
Пример 1. Дескриптор для валидации числовых полей
Дескрипторы мощный механизм, лежащий в основе property. Создадим дескриптор, проверяющий, что значение является положительным числом.
class PositiveNumber:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype):
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"{self.name} должно быть положительным числом")
obj.__dict__[self.name] = value
class Rectangle:
width = PositiveNumber()
height = PositiveNumber()
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(10, 5)
print(r.area())
try:
r.width = -5
except ValueError as e:
print(e)
50 width должно быть положительным числом
Метод __set_name__ автоматически вызывается при создании класса и запоминает имя атрибута. __get__ возвращает значение из словаря экземпляра, __set__ проверяет и сохраняет. Это позволяет избежать дублирования логики валидации.
Пример 2. Метакласс для автоматической регистрации подклассов
Метаклассы управляют созданием классов. Реализуем метакласс, который собирает все неабстрактные подклассы в реестр.
class RegistryMeta(type):
registry = {}
def __new__(cls, name, bases, dct):
new_class = super().__new__(cls, name, bases, dct)
if not dct.get('_abstract'):
cls.registry[name] = new_class
return new_class
class Base(metaclass=RegistryMeta):
_abstract = True
class Dog(Base):
pass
class Cat(Base):
pass
class Sheep(Base):
pass
print(RegistryMeta.registry)
{'Dog': , 'Cat': , 'Sheep': }
Здесь __new__ метакласса вызывается при каждом создании класса. Если в словаре класса нет ключа '_abstract' (или он False), класс регистрируется. Это удобно для плагинов и систем наследования, где нужен список всех наследников.
Пример 3. Перегрузка операторов для вектора
Переопределив магические методы, можно заставить объекты работать с операторами +, -, ==, abs и другими.
import math
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
def __eq__(self, other):
return (self.x == other.x) and (self.y == other.y)
def __abs__(self):
return math.hypot(self.x, self.y)
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
print(v1 + v2)
print(v1 - v2)
print(v1 == v2)
print(abs(v1))
Vector2D(4, 6) Vector2D(2, 2) False 5.0
Магические методы делают классы естественными для интуитивных операций. Можно перегружать __neg__, __mul__, __truediv__ и многие другие.