Как настроить ввод Python для аргумента типа

Я хотел бы правильно добавить typing для типов python, переданных в качестве аргументов. Например. предположим, что мы хотели бы добавить typing к следующей функции:

def do_something_based_on_types(
    ...
    type_name: str,
    str_to_type_mapping: Dict[str, Any], # what instead of Any?
):
    ...
    object = str_to_type_mapping[type_name]()
    ...

где мы хотели бы передать отображение из str в тип, на основе которого мы хотели бы построить объект выбранного класса. Что такое правильный typing для этого сценария (вместо Any, используемого в примере кода).


person Marcin Możejko    schedule 21.02.2020    source источник
comment
Из вашего вопроса мне не ясно, что именно вы ищете, но я подозреваю, что, возможно, вам нужно сделать либо Dict[str, Callable[[], object]], либо Dict[str, Type[object]].   -  person Michael0x2a    schedule 23.02.2020
comment
Я не думаю, что это дубликат stackoverflow.com/q/44325153, поскольку этот вопрос касается одного типа динамического атрибута. основанный на параметре __init__, а не на сопоставлении типов, содержащем несколько разных типов.   -  person schot    schedule 26.02.2020


Ответы (1)


Ваш пример очень динамичен по своей природе. Поскольку основной целью аннотаций типов Python является поддержка статического анализа, их возможности здесь ограничены. Однако я думаю, что мы можем добиться большего успеха, чем Any!

(Примечание: во избежание путаницы я буду ссылаться на переменную в вашем фрагменте с именем object как на my_object, когда я использую object, я имею в виду тип Python).

Использование typing.Type

Поскольку вы упомянули, что значения в предоставленном словаре должны быть типами, которые могут быть инициированы, разумной аннотацией типа будет либо Dict[str, Type[object]], либо Dict[str, Type], где последний эквивалентен Dict[str, Type[Any]]. Первый является наиболее строгим и ограничивает то, что средство проверки типов позволит вам делать с вашей переменной my_object. Второй лишь немного лучше обычного Any, хотя он предупреждает вызывающего, когда он случайно предоставляет экземпляр вместо типа.

Или typing.Callable?

Из предоставленного вами фрагмента единственный способ использования значения словаря - создать новый экземпляр. Тип — не единственное, что может это сделать. На самом деле, любой вызываемый объект (простая функция, метод класса), который принимает нулевые аргументы и возвращает объект, также будет достаточен. Таким образом, использование Callable[[], object] предоставит больше гибкости вызывающей стороне функции, а также предупредит, когда передается тип, который не имеет конструктора с нулевым аргументом. Я бы предпочел это Type, так как это кажется более безопасным и более в духе утиного набора Python.

Что-то лучше, чем простой объект?

Оба решения имеют ограничение, заключающееся в том, что внутри вашей функции мы ничего не знаем о типе my_object. В общем случае мы «вынуждены» делать isinstance проверок каждый раз, когда выполняем операцию, не поддерживаемую типом объекта. Но, возможно, в вашем случае использования один словарь не должен содержать любой тип. Это может быть так:

  1. Все типы имеют общий базовый класс, скажем, MyBase.
  2. Предполагается, что все типы поддерживают общий набор операций (методов или атрибутов).

В случае (1) мы можем заменить тип object на тип MyBase. Средство проверки типов должно быть в состоянии убедиться, что операции, поддерживаемые MyBase, безопасны для использования, и предупреждать, когда предоставленный словарь содержит другие типы.

В случае (2) нам может помочь typing.Protocol. Вы можете определить собственный протокол с требуемыми операциями и добавить его в аннотацию типа:

class MyCustomProto(Protocol):
    foo: int
    def bar(self) -> str: ...

Теперь средство проверки типов должно знать, что my_object должен иметь целочисленный атрибут foo и метод bar, возвращающий строку. Обратите внимание, что Protocol было добавлено в Python 3.8 и доступно для предыдущих версий через typing-extensions пакет.

Диктовка против набора текста. Отображение

Не имеет прямого отношения к вашему вопросу, но если вы используете словарь только для чтения, вам следует рассмотреть возможность замены Dict на тип Mapping. Это позволяет вызывающей стороне предоставить любой тип сопоставления, кроме подклассов dict (на практике это маловероятно). Но что еще более важно, он предупреждает вас о случайном изменении предоставленного сопоставления, что может привести к трудностям в поиске ошибок. Таким образом, мое последнее предложение будет Mapping[str, Callable[[], <X>], а <X> будет одним из object, MyCustomProto или MyBase.

person schot    schedule 23.02.2020