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:NamedTuple
s неизменяемы, поэтому имейте в виду, что вы не можете использовать это решение, если хотите изменить поля ваших объектов. Изменение содержимого 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