← На главную

Паттерн непрозрачного типа в Python: typing.NewType

23.05.2026 13:17 · hackernews

Когда пишешь Python-библиотеку, рано или поздно упираешься в объект с настройками — например, опции доставки. Сначала кажется, что хватит обычного dataclass с парой полей. Но требования растут: нужны разные скорости, перевозчики, виды транспорта. Если сразу выставить публичный класс с конструктором, каждый раз при добавлении новой опции сломаешь клиентов. Автор статьи предлагает паттерн непрозрачного типа (opaque data type) через typing.NewType.

Идея простая. Создаётся приватный класс — в примере _RealShipOpts с приватными атрибутами. Затем через NewType объявляется публичный тип ShippingOptions, который на самом деле является псевдонимом для _RealShipOpts. Конструктор приватного класса остаётся скрытым. Вместо него библиотека предоставляет набор публичных функций-конструкторов: shipFast(), shipNormal(), shipSlow(). Они возвращают ShippingOptions, внутри создавая экземпляр _RealShipOpts с нужными параметрами.

На первый взгляд это кажется лишней обёрткой. Но главная выгода — возможность менять внутреннюю структуру, не трогая API. Например, изначально в _RealShipOpts было только поле _speed (строка "fast", "normal", "slow"). Позже потребовалось добавить конкретного перевозчика (Carrier.FedEx, USPS, DHL, UPS) и способ перевозки (Conveyance.air, truck, train). Внутренний класс легко переписывается: теперь _RealShipOpts содержит _carrier и _freight. Старые функции shipFast, shipNormal, shipSlow обновляются — внутри они теперь создают объект с соответствующим перевозчиком и видом транспорта. Для клиента же сигнатура этих функций не изменилась, и код, который их вызывает, продолжает работать без единой правки. Внутри библиотеки можно спокойно обращаться к приватным полям через options._carrier и т.д., так как NewType во время выполнения — это просто базовый тип.

Таким образом, паттерн даёт гибкость: публичная часть API остаётся стабильной, а внутренности эволюционируют без страха что-то сломать. Если нужно добавить новые опции — пишется ещё одна функция-конструктор, старые не трогаются. Метод особенно полезен для библиотек, где конфигурационные объекты передаются в глубину, и любое изменение их структуры вызвало бы каскад правок в пользовательском коде.

Читать оригинал →