Как ввести подсказку словаря со значениями разных типов

При объявлении словаря литералом есть ли способ указать, какое значение я ожидаю для определенного ключа?

И затем, для обсуждения: существуют ли руководящие принципы набора текста по словарю в Python? Мне интересно, считается ли плохой практикой смешивание типов в словарях.

Вот пример:

Рассмотрим объявление словаря в __init__ класса:

(отказ от ответственности: я понимаю, что в примере некоторые из записей .elements, вероятно, были бы более подходящими в качестве атрибутов класса, но это ради примера).

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }


class Line:
    def __init__(self, p1: Tuple[float, float], p2: Tuple[float, float]):
        self.p1, self.p2 = p1, p2
        self.vertical = p1[0] == p2[0]
        self.horizontal = p1[1] == p2[1]

когда я печатаю, введите следующее

rec1 = Rectangle(rec1_corners, show=True, name='Nr1')
rec1.sides['f...

Pycharm предложит мне 'front'. Еще лучше, когда я

rec1.sides['front'].ver...

Pycharm предложит .vertical

Таким образом, Pycharm запоминает ключи из объявления литерала словаря в классе __init__, а также ожидаемые типы их значений. Или, скорее: он ожидает, что любое значение будет иметь любой из типов, указанных в литеральном объявлении — вероятно, так же, как если бы я сделал self.elements = {} # type: Union[type1, type2]. В любом случае, я нахожу это очень полезным.

Если выходные данные ваших функций имеют типизированные подсказки, Pycharm также примет это во внимание.

Итак, предполагая, что в приведенном выше примере Rectangle я хотел указать, что pins — это список Pin объектов... если бы pins был обычным атрибутом класса, это было бы

    self.pins = None  # type: List[Pin]

(при условии, что был сделан необходимый импорт)

Есть ли способ дать подсказку того же типа в объявлении литерала словаря?

Следующее не достигает того, что я ищу:

Добавить подсказку типа Union[...] в конце объявления литерала?

            'area': calc_area(corners),
            'pins': None
        }  # type: Union[Line, Tuple[float, float], float, List[Pin]]

Добавление подсказки к каждой строке:

            'area': calc_area(corners),  # type: float
            'pins': None  # type: List[Pin]
        }

Есть ли лучшая практика для такого рода вещей?

Еще немного фона:

Я работаю с Python в PyCharm и широко использую типизацию, так как это помогает мне прогнозировать и проверять мою работу по мере продвижения. Когда я создаю новые классы, я также иногда добавляю в словарь некоторые редко используемые свойства, чтобы не загромождать объект слишком большим количеством атрибутов (это полезно в режиме отладки).


person levraininjaneer    schedule 25.06.2018    source источник
comment
Мой непрошеный совет заключается в том, что вам следует избегать этого. Неплохая практика смешивать типы в словарях Python. На самом деле это делается довольно часто. Может показаться неправильным делать это из строго типизированного языка, но воспользуйтесь этим в python.   -  person Skam    schedule 26.06.2018
comment
Спасибо Скам. Почему вы говорите, что я должен избегать этого?   -  person levraininjaneer    schedule 26.06.2018


Ответы (2)


Вы ищете TypedDict. В настоящее время это расширение только для mypy, но есть планы сделать его официально санкционированным типом в ближайшем будущем. Однако я не уверен, поддерживает ли PyCharm эту функцию.

Итак, в вашем случае вы бы сделали:

from mypy_extensions import TypedDict

RectangleElements = TypedDict('RectangleElements', {
    'front': Line,
    'left': Line,
    'right': Line,
    'rear': Line,
    'cog': float,
    'area': float,
    'pins': Optional[List[Pin]]
})

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }  # type: RectangleElements

Если вы используете Python 3.6+, вы можете ввести все это более элегантно, используя синтаксис на основе классов.

Однако в вашем конкретном случае я думаю, что большинство людей просто сохранят эти фрагменты данных как обычные поля, а не dict. Я уверен, что вы уже обдумали все плюсы и минусы этого подхода, поэтому я не буду рассказывать вам об этом.

person Michael0x2a    schedule 28.06.2018

После еще нескольких исследований лучший обходной путь, который я смог найти, заключался в том, чтобы гарантировать, что класс Pin может вернуть какой-то заполнитель, а затем использовать заполнитель в качестве значения в литеральном объявлении.

следовательно:

class Pin:
    def __init__(self, **kwargs):
        if len(kwargs) == 0:
            return

Теперь, в примере в OP, я мог бы сделать следующее, чтобы добиться того, что хотел:

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
    } 

Однако, если бы у меня был базовый тип (или типы) в качестве записей, это не сработало бы.

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': None
        'lap_times': None
    } 

где color ожидает строку, а lap_times ожидает список с плавающей запятой...

В таком случае лучшим обходным путем будет

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': 'blue'
        'lap_times': [0.,]
    }

    self.elements['color'], self.elements['lap_times'] = None, None

Ни один из них не кажется очень элегантным, поэтому я все еще надеюсь, что кто-то может предложить что-то лучше.

person levraininjaneer    schedule 26.06.2018