如何在python中键入提示具有不同类型值的字典

how to type hint a dictionary with values of different types in python

当将字典声明为文本时,是否有一种方法来键入提示我对特定键期望的值?

然后,讨论一下:有没有关于用Python输入字典的指导原则?我想知道在字典中混合类型是否被认为是不好的做法。

下面是一个例子:

考虑类的__init__中字典的声明:

(免责声明:我意识到在这个例子中,一些.elements条目可能更适合作为类属性,但这是为了这个例子)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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]

当我键入以下内容时

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

Pycharm会为我推荐'front'。更棒的是,当我这样做的时候

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

Pycharm将建议.vertical

所以pycharm会记住类的__init__中字典文字声明中的键,以及它们的值的预期类型。或者更确切地说:它期望任何值都具有文本声明中的任何一种类型——可能与我做过self.elements = {} # type: Union[type1, type2]的情况相同。不管怎样,我觉得它非常有用。

如果函数的输出类型被暗示,那么pycharm也会考虑到这一点。

所以假设在上面的Rectangle示例中,我想指出pinsPin对象的列表…如果pins是一个普通的类属性,它将是

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

(前提是完成了必要的进口)

在字典文字声明中有没有一种方法给出相同的类型提示?

以下内容不符合我的要求:

在文本声明的末尾添加一个Union[...]类型提示?

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

向每行添加类型提示:

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

这种事情有最佳实践吗?

更多背景:

我和pycharm中的python一起工作,并广泛使用了打字,因为它有助于我在工作中预测和验证我的工作。当我创建新的类时,我有时也会将一些不常用的属性丢到字典中,以避免将对象与太多的属性混淆(这在调试模式中很有用)。


您正在查找typeddit。它目前只是一个mypy唯一的扩展,但有计划在不久的将来使它成为一个正式批准的类型。不过,我不确定Pycharm是否支持这个功能。

所以,在你的情况下,你应该这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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+,那么您可以使用基于类的语法更优雅地输入这些内容。

不过,在您的具体案例中,我认为大多数人只会将这些数据存储为常规字段而不是听写。不过,我相信您已经考虑过这种方法的优缺点,因此我将不向您讲授它。


经过更多的调查,目前我能找到的最佳解决方法是确保类Pin可以返回某种占位符,然后在文本声明中使用占位符作为值。

因此:

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

现在,在OP中的示例中,我可以执行以下操作来实现我想要的目标:

1
2
3
4
        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
    }

但是,如果我有一个基本类型(或类型)作为条目,那么这将不起作用。

1
2
3
4
5
6
        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': None
        'lap_times': None
    }

在哪里?color需要一个字符串,并且lap_times希望有一个浮动列表…

在这种情况下,最好的解决办法是

1
2
3
4
5
6
7
8
        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': 'blue'
        'lap_times': [0.,]
    }

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

这些看起来都不太优雅,所以我仍然希望有人能提出更好的建议。