Skip to content

Вопросы на подумать

У нас 2 объекта data, надо рассчитать сколько полных лет, чисто на python, используя библиотеки?

Для двух дат в Python полные годы обычно считают так: сравнивают разницу по годам и при необходимости вычитают 1, если «день‑месяц» второй даты ещё не достиг «день‑месяц» первой.​

Вариант на чистом datetime

from datetime import date

def full_years_between(d1: date, d2: date) -> int:
    if d2 < d1:
        d1, d2 = d2, d1  # чтобы d1 <= d2

    years = d2.year - d1.year
    # если ещё не наступил «день рождения» в году d2, уменьшаем на 1
    if (d2.month, d2.day) < (d1.month, d1.day):
        years -= 1
    return years


a = date(2000, 5, 10)
b = date(2025, 5, 9)
print(full_years_between(a, b))  # 24

Здесь используются только стандартные классы date из модуля datetime, без сторонних пакетов.​

Вариант со сторонней библиотекой

Если разрешены внешние библиотеки, можно использовать python-dateutil с relativedelta, он сам корректно посчитает разницу в годах:​

from datetime import date
from dateutil.relativedelta import relativedelta

d1 = date(2000, 5, 10)
d2 = date(2025, 5, 9)

delta = relativedelta(d2, d1)
full_years = delta.years  # количество полных лет
print(full_years)  # 24

Под «полные годы» в обоих вариантах понимается количество «дней рождения», которые успели пройти между датами.

Этапы создания объекта

  1. Вызов класса: obj = MyClass(args) — класс выступает как конструктор/фабрика.

  2. __new__(cls, *args) — создаёт объект в памяти (часто super().__new__(cls)), возвращает self.

  3. __init__(self, *args) — инициализирует self.attr = value, выполняется на уже созданном объекте.

class Example:
    def __init__(self, value):
        self.value = value  # Инициализация после создания

obj = Example(42)  # Автоматически: __new__ → __init__

Кто "создатель" на разных уровнях * Разработчик: вызывает класс напрямую.

  • Интерпретатор: через new + init (или call для метаклассов).

  • Встроенные типы (int, list): аналогичный механизм, но с предопределёнными реализациями.

Как динамически создать класс в runtime?

В Python динамически создавать классы во время выполнения программы (runtime) можно с помощью функции type() или метаклассов. Динамическое создание классов — это генерация классов программно, а не их явное определение в исходном коде.

С помощью функции type()

Функция type() — встроенный метакласс, который позволяет создавать новые классы.

Синтаксис: type(name, bases, attributes).
Параметры:
name — имя нового класса.
bases — кортеж базовых классов (классов, которые унаследует новый класс).
attributes — словарь с атрибутами будущего класса (обычно со строками в ключах и вызываемыми типами в значениях).

Пример:

MyClass = type('MyClass', (object,), dict()) — создаёт класс с именем MyClass, наследуется от object по умолчанию. 
MyClass = type('MyClass', (), {'x': 42}) — создаёт класс с атрибутом x. 

Атрибуты добавляются в класс на стадии инициализации в качестве третьего аргумента — словаря. В словаре можно указать имена атрибутов и значения. Если нужно, чтобы класс наследовался от другого класса, его передают второму аргументу при определении класса с использованием type().

С помощью метаклассов

Метакласс — специальный класс, который позволяет программно создавать новые классы. Чтобы динамически сгенерировать класс с помощью метакласса, нужно вызвать его с соответствующими аргументами.
По умолчанию Python использует встроенный метакласс — type. Можно создать свой метакласс. Для этого нужно определить новый класс, который наследуется от type. В метаклассе можно определить два специальных метода:
new() — вызывается, когда метакласс используется для создания объекта нового класса, отвечает за создание и возврат объекта нового класса.
init() — вызывается после создания объекта нового класса, отвечает за инициализацию атрибутов класса.

Пример:

class MyMeta(type): pass — определяет новый метакласс MyMeta, который наследуется от type.
class MyClass(metaclass=MyMeta): pass — создаёт класс, который использует MyMeta в качестве метакласса.

Из-за чего Python "медленный"?

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

Интерпретируемость байт-кода
Python не компилируется в машинный код заранее — исходный код преобразуется в промежуточный байт-код (.pyc), который интерпретатор выполняет построчно в runtime через виртуальную машину (PVM). Каждая инструкция требует lookup'ов в словарях, проверки типов и динамических вызовов, что в 10–100 раз медленнее нативного кода C/C++.

Динамическая типизация
Переменные не имеют фиксированного типа — каждый раз при обращении к объекту Python проверяет тип, ищет методы в __dict__ или MRO (Method Resolution Order). Динамические атрибуты (setattr, monkey-patching) и слабая ссылочная семантика добавляют накладные расходы на каждый доступ/вызов.

Global Interpreter Lock (GIL)
В CPython один поток не может выполнять Python-код параллельно с другим из-за GIL — глобальной блокировки, защищающей счётчик ссылок (refcount). Это убивает CPU-bound многопоточность; для параллелизма нужны multiprocessing или async (но только для I/O).

Объектоцентричность всего
Всё в Python — объект с overhead: даже int занимает ~28 байт вместо 4–8 у C-int, плюс GC-паузы и частые аллокации.
Списки/словари — хэш-таблицы с высокой константой, а не массивы в памяти.