Вопросы на подумать
У нас 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
Под «полные годы» в обоих вариантах понимается количество «дней рождения», которые успели пройти между датами.
Этапы создания объекта
-
Вызов класса: obj = MyClass(args) — класс выступает как конструктор/фабрика.
-
__new__(cls, *args)— создаёт объект в памяти (частоsuper().__new__(cls)), возвращает self. -
__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-паузы и частые аллокации.
Списки/словари — хэш-таблицы с высокой константой, а не массивы в памяти.