web-dev-qa-db-ja.com

Python TypeScriptインターフェースと同等

最近、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的な方法を見つけることにまだ成功しませんでした(私の試みは私の趣味にあまりにも多くの決まり文句をもたらしました)。

21
Otto

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

ただし、残念ながら手動で宣言する必要があります。


編集

Michael0x2acomment で指摘したように、デフォルトのコンストラクタの必要性は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でも認識され、元のモジュールが静的に型付けされていない場合、型のヒントとコード補完にスタブファイルを使用します。

14
hoefling

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'),
    ],
)

編集:NamedTuplesは不変なので、オブジェクトのフィールドを変更する場合はこのソリューションを使用できないことに注意してください。 listsdictsの内容を変更しても、引き続き問題ありません。

5
Brian Schlenker

私が見つけた簡単な解決策(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')
    ])
  • これはPython 3.3以降で動作します
  • フィールドは変更可能です(NamedTupleソリューションとは異なります)
  • コード補完はPyCharmでは問題なく動作するようですが、VSCodeでは100%ではありません(そのために issue を上げました)
  • Mypyでのタイプチェックは機能しますが、PyCharmは、たとえばperson.name = 1

誰かがPython 3.7のdataclassデコレーターがより良いと思う理由を指摘できれば、私は聞きたいです。

4
Otto

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 )。コード品質管理中に他のコードチェッカーと同様に使用できます。

3
Laurent LAPORTE

おそらくこれは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 から得た

2
Boris Yakubchik

TypeScriptインターフェイスはJavaScriptオブジェクトを記述します。このようなオブジェクトは、mypy TypedDictで記述される、よく知られた文字列キーを持つPython辞書に類似しています。

TypeScriptインターフェースの例

たとえば、TypeScriptインターフェイスは次のとおりです。

interface Address {
    street: string;
    housenumber: number;
}

次のようなJavaScriptオブジェクトを記述します。

var someAddress = {
    street: 'SW Gemini Dr.',
    housenumber: 9450,
};

mypy TypedDictの例

同等の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,
)
0
David Foster