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' }
    ]
}

довольно сжатый и дающий всю роскошь как проверка типа и завершение кода при кодировании с людьми.

Как это делается в Python?

Я смотрел на Mypy и ABC, но пока не удалось найти питонический способ сделать что-то подобное, как указано выше (мои попытки привели к слишком много шаблонных на мой вкус).

5 ответов


для завершения кода и введите намек в IDEs, просто добавьте статический ввод для 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

но, к сожалению, вы должны объявить их вручную.


редактировать

As Michael0x2a указал на комментарий потребность в конструкторах по умолчанию, можно избежать в python3.7, который представил @dataclass декоратор, так что действительно можно заявить:

@dataclass
class Address:
    street: str
    housenumber: int
    housenumber_postfix: Optional[str]


@dataclass
class Person:
    name: str
    adresses: Sequence[Address]

и получите impl по умолчанию нескольких методов, уменьшая количество кода шаблона. Проверьте Пеп 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

эти файлы заглушки также распознаются IDEs, и если ваш исходный модуль не является статически типизированным, они будут использовать файл заглушки для подсказок типа и завершение кода.


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

Edit:NamedTuples неизменяемы, поэтому имейте в виду, что вы не можете использовать это решение, если хотите изменить поля ваших объектов. Изменение содержимого lists и dicts все еще в порядке.


С Python 3.5, вы можете использовать аннотации для указания типа параметров и возвращаемых типов. Большинство последних IDE, таких как PyCharm, могут интерпретировать эти аннотации и дать вам хорошее завершение кода. Комментарий также можно использовать для указания подписи функции или типа переменной.

вот пример:

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). Его можно использовать как любой другой контролер кода во время проверки качества кода.


простое решение, которое я нашел (которое не требует 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, но не 100% в VSCode (поднял вопрос для этого)
  • проверка типа в mypy работает,но PyCharm не жалуется, если я е. G do person.name = 1

Если кто-нибудь может указать, почему Python 3.7 л!--2--> декоратор был бы лучше, я хотел бы услышать.


возможно, это будет хорошо работать с 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