Python相当于Typescript接口

Ott*_*tto 27 python static-typing typescript mypy

最近我一直在使用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' }
    ]
}
Run Code Online (Sandbox Code Playgroud)

非常简洁,并在使用人员编码时将所有奢侈品作为类型检查和代码完成.

这是如何在Python中完成的?

我一直在看Mypy和ABC但是还没有成功找到pythonic方式做类似上面的事情(我的尝试导致了太多的样板符合我的口味).

hoe*_*ing 19

对于IDE中的代码完成和类型提示,只需为PersonAddress类添加静态类型,您已经很好了.假设你使用最新的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
Run Code Online (Sandbox Code Playgroud)

我想在添加类构​​造函数时会出现你提到的样板文件.这确实是不可避免的.我希望默认构造函数是在未明确声明时在运行时生成的,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

但不幸的是你必须手动声明它们.


编辑

正如Michael0x2a注释中指出的那样,对于默认构造函数的需求是可以避免的,python3.7其中引入了一个@dataclass装饰器,所以人们确实可以声明:

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


@dataclass
class Person:
    name: str
    adresses: Sequence[Address]
Run Code Online (Sandbox Code Playgroud)

并获得几种方法的默认impl,减少样板代码的数量.查看PEP 557了解更多详情.


我想你可以看到可以从你的代码生成的存根文件,作为某种接口文件:

$ stubgen spam  # stubgen tool is part of mypy package
Created out/spam.pyi
Run Code Online (Sandbox Code Playgroud)

生成的存根文件包含所有非私有类的类型签名和模块的功能,而不实现:

# 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
Run Code Online (Sandbox Code Playgroud)

这些存根文件也可以被IDE识别,如果您的原始模块没有静态类型,它们将使用存根文件进行类型提示和代码完成.

  • 关于你的第二个代码片段 - 你可能对最近被接受的[PEP 557](https://www.python.org/dev/peps/pep-0557/)感兴趣(正好赶上Python 3.7!)并专门设计用于解决您提出的确切痛点. (5认同)
  • @LaurentLAPORTE——有趣的是,似乎一个半新的约定是实际使用类注释作为指示需要哪些实例属性的一种方式。有关示例,请参见 PEP 526(特别是 [关于类注释的部分](https://www.python.org/dev/peps/pep-0526/#id9))。[这个问题](/sf/ask/3327273071/)(以及我留在那里的答案)更详细地介绍了这一点。但是,要注意的一件事是,此答案不使用任何类 *attributes*。它使用了几个不同的类*注解*。 (3认同)
  • 类属性在这里没用。 (2认同)

Ott*_*tto 6

我发现一个简单的解决方案(不需要 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')
    ])
Run Code Online (Sandbox Code Playgroud)
  • 这适用于 Python 3.3 及更高版本
  • 字段是可变的(与 NamedTuple 解决方案不同)
  • 代码完成在 PyCharm 中似乎完美无缺,但在 VSCode 中不是 100%(为此提出了一个问题
  • 在 mypy 中进行类型检查工作,但如果我这样做,PyCharm 不会抱怨 person.name = 1

如果有人能指出为什么 Python 3.7 的dataclass装饰器会更好,我很乐意听到。


Dav*_*ter 6

TypeScript 接口描述了一个 JavaScript 对象。这样的对象类似于具有众所周知的字符串键的 Python 字典,它由 mypy TypedDict描述。

TypeScript 接口示例

例如 TypeScript 接口:

interface Address {
    street: string;
    housenumber: number;
}
Run Code Online (Sandbox Code Playgroud)

将描述 JavaScript 对象,如:

var someAddress = {
    street: 'SW Gemini Dr.',
    housenumber: 9450,
};
Run Code Online (Sandbox Code Playgroud)

mypy TypedDict 示例

等效的 mypy TypedDict

from typing_extensions import TypedDict

class Address(TypedDict):
    street: str
    housenumber: int
Run Code Online (Sandbox Code Playgroud)

将描述 Python 字典,如:

some_address = {
    'street': 'SW Gemini Dr.',
    'housenumber': 9450,
}

# or equivalently:

some_address = dict(
    street='SW Gemini Dr.',
    housenumber=9450,
)
Run Code Online (Sandbox Code Playgroud)

这些字典可以简单地与 JSON 序列化,并将符合类似的 TypeScript 接口类型。

注意:如果您使用的是 Python 2 或更旧版本的 Python 3,您可能需要对 TypedDict 使用旧的基于函数的语法:

from mypy_extensions import TypedDict

Address = TypedDict('Address', {
    'street': str,
    'housenumber': int,
})
Run Code Online (Sandbox Code Playgroud)

备择方案

Python 中还有其他方法来表示具有命名属性的结构。

命名元组很便宜并且具有只读键。但是,它们不能自动序列化为 JSON 或从 JSON 序列化。

from typing import NamedTuple

class Address(NamedTuple):
    street: str
    housenumber: int

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)
Run Code Online (Sandbox Code Playgroud)

在 Python 3.7 中可用的数据类具有读写键。它们也不能自动序列化为 JSON 或从 JSON 序列化。

from dataclasses import dataclass

@dataclass
class Address:
    street: str
    housenumber: int

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)
Run Code Online (Sandbox Code Playgroud)

Python 3.3 中提供的简单命名空间与数据类类似,但不是很出名。

from types import SimpleNamespace

class Address(SimpleNamespace):
    street: str
    housenumber: int

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)
Run Code Online (Sandbox Code Playgroud)

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,
)
Run Code Online (Sandbox Code Playgroud)


Bri*_*ker 5

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'),
    ],
)
Run Code Online (Sandbox Code Playgroud)

编辑:NamedTuples是不可变的,因此请注意,如果要修改对象的字段,则不能使用此解决方案。更改的内容lists,并dicts仍然有效。


Bor*_*hik 5

也许这会很好地与 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},
]
Run Code Online (Sandbox Code Playgroud)

mypy 文档源代码中阅读有关TypedDict 的更多信息

我很确定你可以嵌套这些东西Optional如果你愿意,可以设置其中的一些。

我从/sf/answers/1471040441/得到这个想法