Как определить пользовательские типы данных в Python?

Ответ

В Python пользовательские типы данных определяются с помощью классов. Это позволяет создавать собственные структуры данных с уникальными атрибутами и методами, инкапсулируя логику и состояние.

1. Использование классов: Базовый подход — создание класса, который определяет структуру (атрибуты) и поведение (методы) объекта.

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def distance_to_origin(self) -> float:
        """Вычисляет расстояние от точки до начала координат."""
        return (self.x ** 2 + self.y ** 2) ** 0.5

# Пример использования
p = Point(3, 4)
print(f"Точка: ({p.x}, {p.y}), Расстояние до начала координат: {p.distance_to_origin()}")

2. Использование dataclasses (Python 3.7+): Декоратор @dataclass значительно упрощает создание классов, автоматически генерируя методы, такие как __init__, __repr__, __eq__ и другие, на основе аннотаций типов. Это уменьшает объем шаблонного кода и повышает читаемость.

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

    def distance_to_origin(self) -> float:
        """Вычисляет расстояние от точки до начала координат."""
        return (self.x ** 2 + self.y ** 2) ** 0.5

# Пример использования
p = Point(3, 4)
print(f"Точка: {p}, Расстояние до начала координат: {p.distance_to_origin()}")

3. Расширенные возможности:

  • Переопределение операторов: Для создания типов, которые ведут себя как встроенные (например, __add__, __eq__), позволяя использовать стандартные операторы Python.
  • typing.NamedTuple: Для создания неизменяемых, легковесных структур данных, которые поддерживают аннотации типов и доступ по имени поля, как у кортежей. Идеально подходит для простых DTO (Data Transfer Objects).
  • Абстрактные базовые классы (ABC): Для определения интерфейсов, которым должны соответствовать подклассы, обеспечивая полиморфизм и стандартизацию поведения.

Ответ 18+ 🔞

Да ты посмотри, что эти питонисты вытворяют! Ну, типа, есть у них свои коробочки с данными, встроенные, а им, блядь, мало! Хочется свою собственную, чтоб с блёстками и с дыркой для жопы. Ну, ладно, сейчас разберём эту хрень.

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

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def distance_to_origin(self) -> float:
        """Вычисляет расстояние от точки до начала координат."""
        return (self.x ** 2 + self.y ** 2) ** 0.5

# Пример использования
p = Point(3, 4)
print(f"Точка: ({p.x}, {p.y}), Расстояние до начала координат: {p.distance_to_origin()}")

Вот этот __init__ — это типа церемония инициации для твоего объекта. Ты ему говоришь: «Родись, уёбок, вот с такими координатами!». А потом можешь спрашивать: «Ну что, расстояние до центра вселенной посчитал?». И он тебе, такой весь в себе, отвечает.

2. Магия dataclasses, или "Лень — двигатель прогресса" А потом пришли ленивые, но умные ребята и сказали: «Ёпта, ну это же овердохуища кода для такой простой хуйни!». И придумали декоратор @dataclass. Навесил его на класс — и он тебе автоматом прикрутит и __init__, и __repr__ (чтобы красиво печаталось), и __eq__ (чтобы сравнивать можно было). Просто праздник какой-то, а не жизнь!

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

    def distance_to_origin(self) -> float:
        """Вычисляет расстояние от точки до начала координат."""
        return (self.x ** 2 + self.y ** 2) ** 0.5

# Пример использования
p = Point(3, 4)
print(f"Точка: {p}, Расстояние до начала координат: {p.distance_to_origin()}")

Смотри, какая красота! Никакого этого def __init__(self, x, y): self.x = x... — просто объявил поля, и всё, блядь. Питон сам всё понял, он же не дурак. Ну почти.

3. А если захотелось поизвращаться? А, ну это да, тут простор для полёта ебаной фантазии.

  • Переопределение операторов: Хочешь, чтобы твои точки складывались как p1 + p2? Пожалуйста, напиши метод __add__. Хочешь, чтобы их сравнивали как p1 == p2? __eq__ тебе в руки. Сделаешь из класса настоящую мартышку, которая умеет всё, что и встроенные типы.
  • typing.NamedTuple: Это для святых, которые любят неизменяемость. Создал раз — и сиди, любуйся, как на икону. Ничего поменять нельзя, зато память ест мало и работает быстро. Идеально для передачи данных туда-сюда, не боясь, что какой-нибудь мудила их по дороге испортит.
  • Абстрактные базовые классы (ABC): Это уже высший пилотаж, ёпта. Ты говоришь: «Все, кто хочет быть моим сыном, должны уметь делать ТАК и СЯК». А если не умеют — Питон их даже создать не даст, заругается. Ну, типа, жёсткий контроль качества на входе.

Вот и вся философия. Создаёшь свои типы, когда стандартные — говно, которое не решает твою задачу. Главное — не перемудрить, а то получится не тип данных, а пиздопроебибна, которую потом и сам не разберёшь.