最近、TypeScriptを頻繁に使用しています。次のようなことを表現できます。
interface Address {
street: string;
housenumber: number;
housenumberPostfix?: string;
}
interface Person {
name: string;
adresses: Address[]
}
const person: Person = {
name: 'Joe',
adresses: [
{ street: 'Sesame', housenumber: 1 },
{ street: 'Baker', housenumber: 221, housenumberPostfix: 'b' }
]
}
かなり簡潔で、Personsでのコーディング中に型チェックとコード補完としてすべての豪華さを与えます。
これはPythonでどのように行われますか?
私はMypyとABCを見てきましたが、上記と似たようなPython的な方法を見つけることにまだ成功しませんでした(私の試みは私の趣味にあまりにも多くの決まり文句をもたらしました)。
IDEでのコード補完と型ヒントについては、Person
クラスとAddress
クラスに静的型付けを追加するだけで十分です。最新のpython3.6
を使用すると仮定すると、例のTypeScriptクラスの大まかな同等物は次のとおりです。
# spam.py
from typing import Optional, Sequence
class Address:
street: str
housenumber: int
housenumber_postfix: Optional[str]
def __init__(self, street: str, housenumber: int,
housenumber_postfix: Optional[str] = None) -> None:
self.street = street
self.housenumber = housenumber
self.housenumber_postfix = housenumber_postfix
class Person:
name: str
adresses: Sequence[Address]
def __init__(self, name: str, adresses: Sequence[str]) -> None:
self.name = name
self.adresses = adresses
person = Person('Joe', [
Address('Sesame', 1),
Address('Baker', 221, housenumber_postfix='b')
]) # type: Person
クラスコンストラクターを追加すると、お決まりの定型文が現れると思います。これは確かに避けられません。次のように、明示的に宣言されていない場合、実行時にデフォルトのコンストラクタが生成されることを望みます。
class Address:
street: str
housenumber: int
housenumber_postfix: Optional[str]
class Person:
name: str
adresses: Sequence[Address]
if __name__ == '__main__':
alice = Person('Alice', [Address('spam', 1, housenumber_postfix='eggs')])
bob = Person('Bob', ()) # a Tuple is also a sequence
ただし、残念ながら手動で宣言する必要があります。
Michael0x2a が comment で指摘したように、デフォルトのコンストラクタの必要性はpython3.7
デコレータを導入した@dataclass
で回避可能にしたため、実際に宣言できます。
@dataclass
class Address:
street: str
housenumber: int
housenumber_postfix: Optional[str]
@dataclass
class Person:
name: str
adresses: Sequence[Address]
いくつかのメソッドのデフォルト実装を取得し、定型コードの量を減らします。詳細については、 PEP 557 をご覧ください。
あなたは、あなたのコードから生成できるスタブファイルを、ある種のインターフェースファイルとして見ることができると思います:
$ stubgen spam # stubgen tool is part of mypy package
Created out/spam.pyi
生成されたスタブファイルには、実装されていないモジュールのすべての非プライベートクラスおよび関数の型付き署名が含まれます。
# Stubs for spam (Python 3.6)
#
# NOTE: This dynamically typed stub was automatically generated by stubgen.
from typing import Optional, Sequence
class Address:
street: str
housenumber: int
housenumber_postfix: Optional[str]
def __init__(self, street: str, housenumber: int, housenumber_postfix: Optional[str]=...) -> None: ...
class Person:
name: str
adresses: Sequence[Address]
def __init__(self, name: str, adresses: Sequence[str]) -> None: ...
person: Person
これらのスタブファイルはIDEでも認識され、元のモジュールが静的に型付けされていない場合、型のヒントとコード補完にスタブファイルを使用します。
Python 3.6では、型ヒントで機能するnamedtupleの新しい実装が追加されました。これにより、他の回答で必要な定型文の一部が削除されます。
from typing import NamedTuple, Optional, List
class Address(NamedTuple):
street: str
housenumber: int
housenumberPostfix: Optional[str] = None
class Person(NamedTuple):
name: str
adresses: List[Address]
person = Person(
name='Joe',
adresses=[
Address(street='Sesame', housenumber=1),
Address(street='Baker', housenumber=221, housenumberPostfix='b'),
],
)
編集:NamedTuple
sは不変なので、オブジェクトのフィールドを変更する場合はこのソリューションを使用できないことに注意してください。 lists
とdicts
の内容を変更しても、引き続き問題ありません。
私が見つけた簡単な解決策(Python 3.7)を必要としない)は、 SimpleNamespace を使用することです:
from types import SimpleNamespace as NS
from typing import Optional, List
class Address(NS):
street: str
housenumber: int
housenumber_postfix: Optional[str]=None
class Person(NS):
name: str
addresses: List[Address]
person = Person(
name='Joe',
addresses=[
Address(street='Sesame', housenumber=1),
Address(street='Baker', housenumber=221, housenumber_postfix='b')
])
person.name = 1
誰かがPython 3.7のdataclass
デコレーターがより良いと思う理由を指摘できれば、私は聞きたいです。
Python 3.5では、アノテーションを使用してパラメーターのタイプと戻り値のタイプを指定できます。PyCharmなどの最近のIDEのほとんどは、これらのアノテーションを解釈し、適切なコード補完を提供できます。関数のシグネチャまたは変数のタイプを指定するコメント。
以下に例を示します。
from typing import List, Optional
class Address(object):
def __init__(self, street: str, housenumber: int, housenumber_postfix: Optional[str]=None):
self.street = street
self.housenumber = housenumber
self.housenumber_postfix = housenumber_postfix
class Person(object):
def __init__(self, name: str, addresses: List[Address]):
self.name = name
self.addresses = addresses
person = Person(
name='Joe',
addresses=[
Address(street='Sesame', housenumber=1),
Address(street='Baker', housenumber=221, housenumber_postfix='b')
])
Pythonは厳密に型指定された言語ではありません。したがって、注釈は開発者向けのガイドにすぎません。コードを本当に確認したい場合は、外部ツールが必要です。 mypy )。コード品質管理中に他のコードチェッカーと同様に使用できます。
おそらくこれはmypy
でうまく機能するでしょう
from typing import List
from mypy_extensions import TypedDict
EntityAndMeta = TypedDict("EntityAndMeta", {"name": str, "count": int})
my_list: List[EntityAndMeta] = [
{"name": "Amy", "count": 17},
{"name": "Bob", "count": 42},
]
TypedDictの詳細については mypy docs または ソースコード
これらのことをネストする ができると確信しています。必要に応じて Optional
に設定することもできます。
このアイデアは https://stackoverflow.com/a/21014863/5017391 から得た
TypeScriptインターフェイスはJavaScriptオブジェクトを記述します。このようなオブジェクトは、mypy TypedDictで記述される、よく知られた文字列キーを持つPython辞書に類似しています。
たとえば、TypeScriptインターフェイスは次のとおりです。
interface Address {
street: string;
housenumber: number;
}
次のようなJavaScriptオブジェクトを記述します。
var someAddress = {
street: 'SW Gemini Dr.',
housenumber: 9450,
};
同等のmypy TypedDict
:
from typing_extensions import TypedDict
class Address(TypedDict):
street: str
housenumber: int
Pythonのような辞書を記述します:
some_address = {
'street': 'SW Gemini Dr.',
'housenumber': 9450,
}
# or equivalently:
some_address = dict(
street='SW Gemini Dr.',
housenumber=9450,
)
これらのディクショナリは、JSONとの間で簡単にシリアル化でき、類似のTypeScriptインターフェイスタイプに準拠します。
注:Python 2または古いバージョンのPython 3を使用している場合、TypedDictの古い関数ベースの構文を使用する必要があります。
from mypy_extensions import TypedDict
Address = TypedDict('Address', {
'street': str,
'housenumber': int,
})
Pythonには他の方法で名前付きプロパティを持つ構造を表すことができます。
名前付きタプルは安価で読み取り専用キーを持っています。ただし、JSONとの間で自動的にシリアル化することはできません。
from typing import NamedTuple
class Address(NamedTuple):
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
データクラス、Python 3.7で使用可能、読み取り/書き込みキーがあります。JSONとの間で自動的にシリアル化することもできません。
from dataclasses import dataclass
@dataclass
class Address:
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
単純な名前空間、Python 3.3で利用可能)はデータクラスに似ていますが、あまり知られていません。
from types import SimpleNamespace
class Address(SimpleNamespace):
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
attrsは、データクラスに似ていますが、より多くの機能を備えた長年のサードパーティライブラリです。 attrsはmypy typecheckerによって認識されます 。
import attrs
@attr.s(auto_attribs=True)
class Address:
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)