Python等同于Typescript

2024-04-18 22:31:33 发布

您现在位置:Python中文网/ 问答频道 /正文

最近我经常使用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,但还没有成功地找到像上面这样做的pythonic方法(我的尝试导致太多的样板对我的口味)。


Tags: namestreetnumber内容stringaddressinterfaceperson
3条回答

我发现的一个简单的解决方案(不需要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')
    ])
  • 这在Python3.3及更高版本中有效
  • 字段是可变的(与NamedTuple解决方案不同)
  • 代码完成在PyCharm中似乎工作得很完美,但在VSCode中却不是100%(为此提出了一个issue
  • 在mypy中进行类型检查是可行的,但是PyCharm不会抱怨我是否需要person.name = 1

如果有人能指出为什么Python 3.7的dataclassdecorator会更好,我很想听听。

对于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

我想您提到的样板是在添加类构造函数时出现的。这确实是不可撤销的。我希望默认构造函数在运行时生成,而不是显式声明,如下所示:

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]

并获得几个方法的默认impl,从而减少样板代码的数量。查看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是不可变的,因此请注意,如果要修改对象的字段,则不能使用此解决方案。改变listsdicts的内容仍然可以。

相关问题 更多 >