简体   繁体   English

相当于 Typescript 接口的 Python

[英]Python equivalent of Typescript interface

Recently I have been working with Typescript a lot, it allows to express things like:最近我一直在使用 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' }
    ]
}

Pretty concise and giving all the luxuries as type checking and code completion while coding with Persons.非常简洁,并且在使用 Persons 进行编码时提供了类型检查和代码完成等所有奢侈品。

How is this done in Python?这是如何在 Python 中完成的?

I have been looking at Mypy and ABC but did not yet succeed in finding the pythonic way to do something similar as the above (my attempts resulted in way too much boilerplate to my taste).我一直在研究 Mypy 和 ABC,但还没有成功地找到 Python 的方式来做与上述类似的事情(我的尝试导致太多样板文件不符合我的口味)。

For the code completion and type hinting in IDEs, just add static typing for the Person and Address classes and you are already good to go.对于 IDE 中的代码完成和类型提示,只需为PersonAddress类添加静态类型就可以了。 Assuming you use the latest python3.6 , here's a rough equivalent of the typescript classes from your example:假设您使用最新的python3.6 ,这是您示例中的打字稿类的粗略等价物:

# 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

I suppose the boilerplate you mentioned emerges when adding the class constructors.我想你提到的样板是在添加类构​​造函数时出现的。 This is indeed inavoidable.这确实是不可避免的。 I would wish default constructors were generated at runtime when not declared explicitly, like this:我希望在未明确声明时在运行时生成默认构造函数,如下所示:

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

but unfortunately you have to declare them manually.但不幸的是,您必须手动声明它们。


Edit编辑

As Michael0x2a pointed out in the comment , the need for default constructors is made avoidable in python3.7 which introduced a @dataclass decorator, so one can indeed declare:正如Michael0x2a评论中指出的那样,在引入@dataclass装饰器的python3.7中可以避免对默认构造函数的需求,因此确实可以声明:

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


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

and get the default impl of several methods, reducing the amount of boilerplate code.并获取几种方法的默认impl,减少样板代码量。 Check out PEP 557 for more details.查看PEP 557了解更多详情。


I guess you could see stub files that can be generated from your code, as some kind of interface files:我猜你可以看到可以从你的代码生成的存根文件,作为某种接口文件:

$ stubgen spam  # stubgen tool is part of mypy package
Created out/spam.pyi

The generated stub file contains the typed signatures of all non-private classes and functions of the module without implementation:生成的存根文件包含模块的所有非私有类和函数的类型签名,没有实现:

# 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

These stub files are also recognized by IDEs and if your original module is not statically typed, they will use the stub file for type hints and code completion.这些存根文件也被 IDE 识别,如果您的原始模块不是静态类型的,它们将使用存根文件进行类型提示和代码完成。

A TypeScript interface describes a JavaScript object. TypeScript 接口描述了一个 JavaScript 对象。 Such an object is analogous to a Python dictionary with well-known string keys, which is described by a mypy TypedDict .这样的对象类似于具有众所周知的字符串键的 Python 字典,由 mypy TypedDict描述。

TypeScript interface example TypeScript 接口示例

For example the TypeScript interface:例如 TypeScript 接口:

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

will describe JavaScript objects like:将描述 JavaScript 对象,例如:

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

mypy TypedDict example mypy TypedDict 示例

The equivalent mypy TypedDict :等效的 mypy TypedDict

from typing import TypedDict

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

will describe Python dictionaries like:将描述 Python 字典,例如:

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

# or equivalently:

some_address = dict(
    street='SW Gemini Dr.',
    housenumber=9450,
)

These dictionaries can be serialized to/from JSON trivially and will conform to the analogous TypeScript interface type.这些字典可以很容易地序列化到 JSON 或从 JSON 序列化,并且将符合类似的 TypeScript 接口类型。

Note: If you are using Python 2 or older versions of Python 3, you may need to use the older function-based syntax for TypedDict:注意:如果您使用的是 Python 2 或更早版本的 Python 3,则可能需要对 TypedDict 使用旧的基于函数的语法:

from mypy_extensions import TypedDict

Address = TypedDict('Address', {
    'street': str,
    'housenumber': int,
})

Alternatives备择方案

There are other ways in Python to represent structures with named properties. Python 中还有其他方法可以表示具有命名属性的结构。

Named tuples are cheap and have read-only keys. 命名元组很便宜并且具有只读键。 However they cannot be serialized to/from JSON automatically.但是,它们不能自动序列化到 JSON 或从 JSON 序列化。

from typing import NamedTuple

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

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)

Data classes , available in Python 3.7, have read-write keys. Python 3.7 中可用的数据类具有读写键。 They also cannot be serialized to/from JSON automatically.它们也不能自动序列化到 JSON 或从 JSON 序列化。

from dataclasses import dataclass

@dataclass
class Address:
    street: str
    housenumber: int

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)

Simple namespaces , available in Python 3.3, are similar to data classes but are not very well known. Python 3.3 中提供的简单命名空间类似于数据类,但不是很为人所知。

from types import SimpleNamespace

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

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)

attrs is a long-standing third-party library that is similar to data classes but with many more features. attrs是一个长期存在的第三方库,类似于数据类,但具有更多功能。 attrs is recognized by the mypy typechecker . attrs 被 mypy 类型检查器识别

import attrs

@attr.s(auto_attribs=True)
class Address:
    street: str
    housenumber: int

my_address = Address(
    street='SW Gemini Dr.',
    housenumber=9450,
)

Python 3.6 added a new implementation of namedtuple that works with type hints, which removes some of the boilerplate required by the other answers. 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 are immutable, so be aware that you can't use this solution if you want to modify the fields of your objects.编辑: NamedTuple是不可变的,因此请注意,如果要修改对象的字段,则不能使用此解决方案。 Changing the contents of lists and dicts is still fine.更改listsdicts的内容仍然可以。

A simple solution I found (that doesn't require Python 3.7) is to use SimpleNamespace :我发现的一个简单解决方案(不需要 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')
    ])
  • This works in Python 3.3 and higher这适用于 Python 3.3 及更高版本
  • The fields are mutable (unlike NamedTuple solution)字段是可变的(与 NamedTuple 解决方案不同)
  • Code completion seems to work flawlessly in PyCharm but not 100% in VSCode (raised an issue for that)代码完成在 PyCharm 中似乎完美无缺,但在 VSCode 中却不是 100%(为此提出了一个问题
  • Type checking in mypy works, but PyCharm does not complain if I eg do person.name = 1在 mypy 中进行类型检查工作,但如果我做person.name = 1 ,PyCharm 不会抱怨

If anyone can point out why Python 3.7's dataclass decorator would be better I would love to hear.如果有人能指出为什么 Python 3.7 的dataclass装饰器会更好,我很想听听。

Perhaps this will work well with mypy也许这对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},
]

Read more about TypedDict from the mypy docs or from the source codemypy 文档源代码中阅读有关TypedDict的更多信息

I'm pretty sure you can nest these things , and set some of them to Optional if you'd like.我很确定您可以嵌套这些东西,如果您愿意,可以将其中一些设置为Optional

I got this idea from https://stackoverflow.com/a/21014863/5017391我从https://stackoverflow.com/a/21014863/5017391得到这个想法

With Python 3.5, you can use annotations to specify the type of parameters and return types.在 Python 3.5 中,您可以使用注解来指定参数的类型和返回类型。 Most of recent IDE, like PyCharm can interpret those annotations and give you good code completion.大多数最新的 IDE,如 PyCharm 都可以解释这些注释并为您提供良好的代码完成。 You can also use a comment to specify the signature of a function, or the type of a variable.您还可以使用注释来指定函数的签名或变量的类型。

Here is an example:这是一个例子:

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

Notice that Python is not a strongly typed language.请注意,Python 不是强类型语言。 So, annotations are only a guide for developers.因此,注释只是开发人员的指南。 If you really want to check your code, you need an external tools (currently, the best one is mypy ).如果你真的想检查你的代码,你需要一个外部工具(目前最好的是mypy )。 It can be used like any other code checker during code quality control.在代码质量控制期间,它可以像任何其他代码检查器一样使用。

Try https://github.com/cs-cordero/py-ts-interfaces试试https://github.com/cs-cordero/py-ts-interfaces

It looks pretty nice.它看起来很不错。 Quote:引用:

In web applications where Python is used in the backend and TypeScript is used in the frontend, it is often the case that the client will make calls to the backend to request some data with some specific pre-defined "shape".在后端使用 Python 而前端使用 TypeScript 的 Web 应用程序中,通常情况下客户端会调用后端来请求一些具有特定预定义“形状”的数据。 On the client-side, an interface for this data is usually defined and if the Python backend authors use typechecking, like with mypy, the project authors may be typing the JSON response values as well.在客户端,通常会定义此数据的接口,如果 Python 后端作者使用类型检查,例如 mypy,项目作者也可能会键入 JSON 响应值。

This results in a duplication of code.这会导致代码重复。 If the shape changes in the backend, the related interface must also be reflect its changes in the frontend.如果后端的形状发生变化,相关界面也必须在前端反映其变化。 At best, this is annoying to maintain.充其量,这很烦人。 At worst, over time the interfaces may diverge and cause bugs.在最坏的情况下,随着时间的推移,接口可能会出现分歧并导致错误。

This library aims to have a single source of truth that describes the shape of the payload between the backend and the frontend.该库旨在拥有一个单一的事实来源,描述后端和前端之间有效负载的形状。

What do you guys think about pydantic?大家觉得pydantic怎么样? python 3.10蟒蛇 3.10

from pydantic import BaseModel


class Address(BaseModel):
    street: str
    housenumber: int
    housenumberPostfix: str | None = None


class Person(BaseModel):
    name: str
    adresses: list[Address]


person: Person = Person(
    name="Joe",
    adresses=[
        Address(street="Sesame", housenumber=1),
        Address(street="Baker", housenumber=221, housenumberPostfix="b"),
    ],
)

When we input wrong type.当我们输入错误的类型时。

Editor complain wrong type编辑抱怨错误的类型

It has suggestion.它有建议。

Editor show suggestion编辑展示建议

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM