web-dev-qa-db-ja.com

異なるタイプの値を持つ辞書にタイプヒントを付ける方法

辞書をリテラルとして宣言する場合、特定のキーに期待する値をタイプヒントする方法はありますか?

そして、議論のために: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の例では、pinsPinオブジェクトのリストであることを示したかったのです... pinsが通常のクラスの場合属性、それは

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

(必要なインポートが行われた場合)

辞書リテラル宣言で同じ型のヒントを与える方法はありますか?

以下はnotが私が探しているものを達成します:

リテラル宣言の最後に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]
        }

この種のベストプラクティスはありますか?

もう少し背景:

私はPyCharmでPython=を使用しており、タイピングを広範囲に使用しています。これは、作業を進めるにつれて、作業を予測および検証するのに役立つためです。新しいクラスを作成するときは、時々少し投げる属性が多すぎるオブジェクトが乱雑にならないように、頻繁に使用されるプロパティを辞書に入れます(これはデバッグモードで役立ちます)。

6
levraininjaneer

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+を使用している場合、 クラスベースの構文 を使用してこれをよりエレガントに入力できます。

しかし、あなたの特定のケースでは、ほとんどの人はそれらのデータを辞書ではなく通常のフィールドとして保存するだけだと思います。このアプローチの長所と短所はすでにご存じだと思いますので、そのことについての説明はスキップします。

6
Michael0x2a

もう少し調査した後、私がこれまでに見つけた最善の回避策は、クラス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は、floatを含むリストを期待しています...

そのような場合、最善の回避策は

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

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

これらはどちらも非常にエレガントに見えないので、誰かがより良いものを提案できることを望んでいます。

0
levraininjaneer