1.1Kпросмотров
11 мая 2024 г.
statsScore: 1.2K
Там в Python 3.12 добавили нашумевший PEP 659, а у меня пет-проект один давно не обновлялся, и так уж звёзды сошлись, что я сижу второй день обновляю его на 3.12 Задача - есть функционал, который под капотом имеет некоторый класс следующего вида: class BaseFunction: serialize_to: None def serialize(self, data: dict) -> serialize_to: pass # тут мы используем наш serialize_to @dataclass
class ModelA: x: str class FunctionA(BaseFunction): serialize_to: ModelA Мы определяем новые классы наследуясь от BaseFunction, переопределяем в них serialize_to и вызываем serialize который делает нам инстанс serialize_to. Ну прямо дженерик напрашивается! Тем более в 3.12 их завезли, красивые: class BaseFunction[T]: def serialize(self, data: dict) -> T: pass # тут мы используем наш serialize_to class FunctionA(BaseFunction[ModelA]): pass Встаёт вопрос, а как нам получить наш тип из дженерика? Для начала, получим __orig_bases[0] - он вернёт нам классы, от которых мы наследовались. Так как нам нужен только наш первый класс, мы указываем [0]: >>> FunctionA.orig_bases
main.BaseFunction[main__.ModelA] Ещё можно это сделать с помощью get_original_bases из types, но его добавили только в 3.12 (почему я об этом сказал - узнаете ниже). Теперь надо получить получить сам тип в дженерике. В этом нам поможет typing.get_args, который получает все аргументы типа. Дополнительно укажем, что нам нужен первый тип: >>> get_args(FunctionA.__orig_bases[0])[0]
<class 'main__.ModelA'> Теперь в методе serialize класса BaseFunction[T] можно написать штуку, которая автоматически сериализует наши данные: def serialize(self, data: dict) -> T: type_from_generic = get_args(self.class.__orig_bases__[0])[0] return type_from_generic(**data) Проверяем: >>> f = FunctionA()
>>> f.serialize(data={"x": 1})
ModelA(x=1)
Вы восхитительны! Кстати, эта же штука должна работать ещё вроде как аж с 3.8, так как в нём именно был добавлен __orig_bases (PEP 560), ну и под капотом у новых дженериков используется... >>> FunctionA.mro
(<class 'main.FunctionA'>, <class 'main__.BaseFunction'>, <class 'typing.Generic'>, <class 'object'>) Ага, typing.Generic :) #рецепт #std