辞書をリテラルとして宣言する場合、特定のキーに期待する値をタイプヒントする方法はありますか?
そして、議論のために: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]
(必要なインポートが行われた場合)
辞書リテラル宣言で同じ型のヒントを与える方法はありますか?
以下は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=を使用しており、タイピングを広範囲に使用しています。これは、作業を進めるにつれて、作業を予測および検証するのに役立つためです。新しいクラスを作成するときは、時々少し投げる属性が多すぎるオブジェクトが乱雑にならないように、頻繁に使用されるプロパティを辞書に入れます(これはデバッグモードで役立ちます)。
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+を使用している場合、 クラスベースの構文 を使用してこれをよりエレガントに入力できます。
しかし、あなたの特定のケースでは、ほとんどの人はそれらのデータを辞書ではなく通常のフィールドとして保存するだけだと思います。このアプローチの長所と短所はすでにご存じだと思いますので、そのことについての説明はスキップします。
もう少し調査した後、私がこれまでに見つけた最善の回避策は、クラス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
これらはどちらも非常にエレガントに見えないので、誰かがより良いものを提案できることを望んでいます。