TypeScript开发人员高级Python类型现场指南

使用抽象基类和协议在Python中实现类似Typescript的联合,交叉,接口,泛型和组合 (Implementing Typescript-like unions, intersections, interfaces, generics and composition in Python using Abstract Base Classes and Protocols)

Many developers developing polyglot systems or services will be working with the usual language suspects: Javascript, Python, Go, Java to name a few.

许多开发多语言系统或服务的开发人员将使用常见的语言嫌疑人:Javascript,Python,Go,Java等。

Typescript’s unprecedented rise in popularity over recent years — due largely to its smooth, slick syntax and style — has prompted me to write a guide for those developers who are usually focused on Typescript services but may occasionally need to work on some Python, as well as those who program Python and want to start to get their head around the more complex typings that are included in the standard library typing module.

近年来,Typescript的流行度空前提高-很大程度上是由于其流畅,流畅的语法和样式-促使我为那些通常专注于Typescript服务但有时可能需要使用某些Python的开发人员编写指南。那些使用Python进行编程并希望开始关注标准库typing模块中包含的更复杂typing

This guide will not delve into simple base type declarations like strings or numbers but will focus on those often not learned and complex types, including union types, intersection types, composable types, implicit vs explicit implementation of interfaces, and generics. Nor will it go beyond the brief explanation of the differences between Typescript and Python typing modules outlined below, as further resources for these are abundant and can be found at numerous other locations.

本指南不会深入研究诸如字符串或数字之类的简单基本类型声明,而是将重点放在那些通常不易学习的复杂类型上,包括联合类型,交集类型,可组合类型,接口的隐式与显式实现以及泛型。 它也不会超出下面概述的Typescript和Python输入模块之间的区别的简要说明,因为它们的更多资源十分丰富,并且可以在许多其他地方找到。

In short, Typescript is a superset language of Javascript that is compiled to Javascript before execution and stripped of type information (except for in the Deno runtime), whereas Python types are part of the Python language and exist at runtime (incurring a little overhead in the process), meaning they can be inspected at runtime using the signature object, which is part of the builtin inspect module. Python types are used by various static / type analysis tools to analyse your code — such tools will not be explored in this article — though note that I have used Mypy and Pylance for this article to generate the lint messages.

简而言之,Typescript是Javascript的超集语言,在执行之前会被编译为Javascript,并剥离类型信息(在Deno runtime中除外),而Python类型是Python语言的一部分,并且在运行时存在(在Java 语言中会产生一些开销)流程),这意味着可以在运行时使用signature 对象对其进行检查,该对象是内置inspect模块的一部分。 各种静态/类型分析工具都使用Python类型来分析您的代码(本文将不探讨此类工具),但请注意,本文中我使用MypyPylance生成了皮棉消息。

Lastly, the Typescript side of this guide supports all recent typescript versions, while the approach for the Python side is split between implementation using abstract base classes (as introduced in Python version 3) and using Protocols (introduced in Python version 3.8). If the code you are working on is using 3.8 or later feel free to try either of the methods (though I recommend Protocols), while those working on earlier versions will be limited to implementation through abstract base classes only, though I do recommend using protocols as they are a much cleaner interface approaching Typescript style.

最后,本指南的Typescript端支持所有最新的Typescript版本,而Python端的方法则分为使用abstract base classes (如Python版本3中引入)和使用Protocols (如Python版本3.8中引入)的实现。 如果您正在使用的代码使用3.8或更高版本,请随时尝试使用这两种方法(尽管我建议使用Protocols ),而使用早期版本的代码将仅限于通过抽象基类实现,尽管我建议您使用协议因为它们是接近Typescript样式的更简洁的界面。

递归数据类型 (Recursive data types)

Analogues to these Typescript data class types in Python are found by using the dataclasses module, which is used to explicitly implement types. Implementation without dataclasses can be achieved using standard classes and the @property decorator, though dataclasses take a lot of the boilerplate out of constructing simple data container classes.

类似物在Python这些打字稿数据类类型,通过使用所找到的dataclasses模块,其用于显式实现的类型。 使用dataclasses类和@property装饰器可以实现不使用dataclasses实现,尽管dataclasses在构造简单的数据容器类时会占用很多样板。

Note: Dataclasses were introduced in Python 3.7, though there is a popular compatibility library that can be found on pypi called dataclasses that backfills the module for Python 3.6.

注意 :数据是在Python 3.7中引入的,尽管在pypi上可以找到一个流行的兼容性库,称为dataclasses ,它可以为Python 3.6回填模块。

For an implicit approach to typing — more akin to Typescript — Python offers the Protocol class. Protocol allows types to be checked implicitly by checking the interface matches during type assignment, though it only checks methods and variables and not the types directly.

对于隐式键入方法(类似于Typescript),Python提供了Protocol类。 Protocol允许通过在类型分配期间检查接口匹配来隐式检查类型,尽管它只检查方法和变量,而不直接检查类型。

Note, however, that Protocol was only introduced in Python 3.8, so if you are on a previous version you’ll have to fallback to explicit methods and use either dataclasses or the more verbose class implementations.

但是请注意,该Protocol仅在Python 3.8中引入,因此,如果您使用的是以前的版本,则必须回退到显式方法,并使用dataclasses或更详细的类实现。

As shown in the above, I had to use the PersonProtocol instead of the recursive Person type for the friends property of the Person class. This is due to an issue in the mypy tool. If you’re using pylance for type analysis you can use the correct Person as the recursive type.

如上所示,我只好用PersonProtocol而不是递归的Person类型的的朋友产权Person类。 这是由于mypy工具存在问题。 如果您使用pylance进行类型分析,则可以使用正确的Person作为递归类型。

Whilst the use of implicit types via Protocol may seem more verbose, the opportunity is much greater in large codebases where you don’t have to directly import types across service boundaries, and is better when using a library where you don’t have access to the underlying classes falling back on duck typing.

尽管通过Protocol使用隐式类型似乎更为冗长,但是在大型代码库中,您不必跨服务边界直接导入类型的机会就更大了;在使用无法访问服务的库时,这种方法会更好基础类依赖于鸭子类型。

联合类型 (Union Types)

Union types allow the variable to take a form of any of the types listed in the union. This is particularly useful for restricting generic functions to only a few types, or for variables that could take one of several forms.

联合类型允许变量采用联合中列出的任何类型的形式。 这对于将泛型函数限制为仅几种类型或对于可能采用多种形式之一的变量特别有用。

Typescript has a direct operator for this, whereby using the pipe | you imply the type to be a union.

Typescript为此具有直接运算符,从而使用| 您暗示该类型为工会。

As Python typings are not compiled they must stick within the bounds of the language, so instead of an operator you must import the generic Union from the typing module leading to a more verbose implementation.

由于未编译Python类型,因此它们必须位于语言的范围之内,因此必须从类型模块中导入通用Union ,以实现更为冗长的实现,而不是运算符。

It should be noted also that any optional type is in fact a union type meaning: the optionalOptional[int] is equivalent to the unionUnion[int, None] in Python and the optional{a?: number} is equivalent to the union{ a: number | undefined } in Typescript, whereby the former shorter syntax for both languages is just syntactic sugar for the latter.

还应该注意的是,任何可选类型实际上都是联合类型,其含义是:可选Optional[int]等效于Python中的Union[int, None] ,而可选{a?: number}则等效于Union[int, None] { a: number | undefined } Typescript中的{ a: number | undefined } ,前者对两种语言的较短语法只是后者的语法糖。

交叉点类型 (Intersection types)

Intersection types are used to combine interface specifications and are often used as an alternative to class inheritance or as an enhancement to duck typing.

交叉点类型用于组合接口规范,通常用作类继承的替代方法或对鸭子类型的增强。

Intersection types in Python are currently lacking a simplistic approach (with long standing debates about whether they should be included or not). For now at least you are required to construct ‘container’ classes that extend off the types you want to intersect. As before, there are two ways to do this: one explicit via the use of dataclasses, and the other implicit using Protocols.

Python中的交集类型目前缺乏一种简化的方法(关于是否应包含它们的长期争论)。 到目前为止,至少您需要构造“容器”类,该类扩展了您要相交的类型。 和以前一样,有两种方法可以做到这一点:一种通过使用dataclasses显式,另一种通过Protocols隐式。

使用数据类和容器类的显式交集 (Explicit intersection using data classes with a container class)

The dataclass approach requires a container class with a cumbersome initialiser method that forwards the arguments to the correct dataclasses __init__ methods.

dataclass方法需要具有繁琐的初始化程序方法的容器类,该方法将参数转发给正确的数据类__init__方法。

使用带有容器类的协议的隐式交集 (Implicit intersection using Protocols with a container class)

This is streamlined in the Protocol based approach, with the container class simply wrapping the base protocols.

这在基于Protocol的方法中得到了简化,容器类仅包装了基本协议。

泛型 (Generics)

With any level of complexity you will find yourself writing generic functions or interfaces that offer utility on generic types.

无论复杂程度如何,您都会发现自己编写了泛型函数或接口,这些函数或接口为泛型类型提供实用程序。

Below are the approaches to implementing a single generic type function, as well as a multi generic type function to compare the language’s specifications for this.

下面是实现单个泛型类型函数以及用于比较语言规范的多泛型类型函数的方法。

Again, because Python typings are not compiled, we find the Python approach is a little more verbose in having to declare the types within the body of the code before use as generic arguments.

同样,由于未编译Python类型,因此我们发现Python方法在用作通用参数之前必须先在代码主体中声明这些类型,这使它更加冗长。

介面 (Interfaces)

Interfaces in Typescript are used to define object methods and properties without explicitly stating what type the object should be, offering a syntactical contract that the object should implement.

Typescript中的接口用于定义对象的方法和属性,而无需明确说明对象应为哪种类型,从而提供对象应实现的语法协定。

Interestingly, Python does not have a direct analogue to interfaces like Typescript. Python first opted instead to implement a variant of abstract base classes (as found in Java and C++) available through the standard library abc (available since Python 2.7).

有趣的是,Python没有类似于Typescript之类的接口的直接类似物。 Python首先选择通过标准库abc (自Python 2.7起可用)实现抽象基类(在Java和C ++中找到)的变体。

Abstract base classes are a very feature rich and advanced feature in Python so I will not go into detail about what else the classes can offer here. Further information relating to abc’s usage can be found in the python docs with the rationale of abc’s and how they relate to interfaces found in PEP 3119.

抽象基类是Python中非常丰富的高级功能,因此在这里我不会详细介绍这些类。 有关abc用法的更多信息可以在python文档中找到,其中包含abc的原理以及它们与PEP 3119中的接口的关系。

抽象基类 (Abstract Base Classes)

Using the ABC base class with the abstractmethod decorator requires any classes inheriting from the abstract class to overwrite any method that is decorated with the abstractmethod decorator during instantiation. This offers certain runtime utilities as it essentially forces users of the abstract classes to implement the methods. As classes inherit from the abstract classes explicitly you can check during runtime whether they are instances of the abstract classes directly.

ABC基类与abstractmethod装饰器一起使用时,需要从抽象类继承的任何类都可以在实例化过程中覆盖使用abstractmethod装饰器装饰的任何方法。 这提供了某些运行时实用程序,因为它实际上迫使抽象类的用户实现方法。 由于类明确地继承自抽象类,因此您可以在运行时检查它们是否直接是抽象类的实例。

Also note here that in place of pass, where it is commonly used to denote functions that are not implemented, I choose to use the builtin ellipsis type to indicate that the methods are mock methods. This is also commonly found (though not necessarily standard) in type mocks of external libraries.

还要注意,在这里代替pass ,它通常用于表示未实现的功能,我选择使用内置的省略号类型 来表示这些方法是模拟方法。 这在外部库的类型模拟中也很常见(尽管不一定是标准的)。

ABC style approach has runtime checks upon initialisation of subclasses, however subclasses need to extend from these ABC base classes.

ABC样式方法在初始化子类时具有运行时检查,但是子类需要从这些ABC基类扩展。

通讯协定 (Protocols)

Protocol has the disadvantage of no runtime initialisation checks, however classes do not need to extend off the protocol allowing for much more generic programming. Checks can be made against these protocol interfaces with isinstance(X, FileHandler) in runtime (though only if decorated with the @runtime_checkableas below) to make sure X implements the methods (though does not check types of arguments of return types).

协议的缺点是没有运行时初始化检查,但是类不需要扩展协议,从而可以进行更通用的编程。 可以在运行时使用isinstance(X, FileHandler)对这些协议接口进行isinstance(X, FileHandler)尽管仅当使用@runtime_checkable进行如下装饰时),以确保X实现了这些方法(尽管不检查返回类型的参数类型)。

组成和扩展 (Composition and Extension)

Often you will want to build upon another interface and augment it with additional properties or methods. This can be accomplished in Typescript by using either intersections or by extending from another interface as demonstrated below.

通常,您将需要建立在另一个接口上,并使用其他属性或方法对其进行扩充。 可以在Typescript中通过使用intersections或从另一个interface扩展来完成此interface ,如下所示。

Similar to the methods of intersection for Python there is again two ways to do this in Python: one via abstract base classes, and the other via protocols.

与Python的交集方法类似,在Python中还是有两种方法:一种是通过抽象基类,另一种是通过协议。

抽象基类 (Abstract Base Classes)

Due to the requirement of usage of the @abstractmethod decorator in ABCs, and the fact they don’t work on the shorter public class attribute syntax, you are required to declare public class attributes verbosely with @property decorator and a property method as demonstrated below.

由于ABC中必须使用@abstractmethod装饰器,并且它们不能在较短的公共类属性语法上工作,因此,您需要使用@property装饰器和属性方法详细声明公共类属性,如下所示。

Composition is achieved through class based inheritance.

通过基于类的继承来实现合成。

通讯协定 (Protocols)

Instead of using the @property decorator as is required with use of the @abstractmethod in ABCs, we can instead use the much cleaner and less verbose style of declaring public class attributes via the <name>:<type> syntax.

而不是使用的@property因为需要与使用的装饰@abstractmethodABCs ,我们可以改用更清洁和更简洁的风格通过的声明公共类属性<name>:<type>语法。

Inheriting other Protocol base classes is done using the same approach found in the intersection section above, leading to composability through inheritance.

继承其他协议基类的方法上面交叉部分中介绍的方法相同,可通过继承实现可组合性。

混合类型 (Hybrid types)

These are handy types that extend regularly used classes (such as functions), and augment them with additional functionality or properties. The following snippets build a Dog object that can be used like a function, but also has an additional property type which represents the breed of the dog.

这些是方便使用的类型,它们扩展了常用的类(例如函数),并通过附加的功能或属性对其进行了扩充。 下面的代码片段构建了一个Dog对象,该对象可以像函数一样使用,但是还具有表示狗的品种的其他属性type

In Python the same can be achieved through the use of Protocols or abstract bases classes (omitted for simplicity).

在Python中,可以通过使用Protocols或抽象基类(为简单起见而省略)来实现相同目的。

隐式与显式类型转换 (Implicit vs Explicit type conversion)

Often you find yourself working with variables that have the same base type but should be interpreted very different. Classic examples of this are: time (second or millisecond), temperature (Celsius or Fahrenheit), or currency (USD or AUD).

通常,您会发现自己使用的变量具有相同的基本类型,但应解释为截然不同的变量。 典型的例子是:时间(秒或毫秒),温度(摄氏度或华氏度)或货币(美元或澳元)。

Typescript does implicit conversions between types automatically as seen in the following example, which leads to complications around simple variable types.

如下面的示例所示,Typescript会自动在类型之间进行隐式转换,这将导致简单变量类型的复杂化。

Typescript’s implicit type conversions can cause issues when variables of the same base type should be interpreted very differently such as Fahrenheit and Celsius units.
当对相同基本类型的变量(例如华氏温度和摄氏温度单位)进行非常不同的解释时,Typescript的隐式类型转换会引起问题。

Python has a construct called NewType that prevents implicit conversions when used to construct named types and it achieves exactly what we are after.

Python具有一个称为NewType的构造,该构造在用于构造命名类型时可以防止隐式转换,并且可以实现我们所追求的目标。

To achieve these same properties in Typescript you need a bit more of a complex type, which is usually a good place for enums to be applied.

为了在Typescript中实现这些相同的属性,您需要更多一些复杂的类型,这通常是应用enums的好地方。

加起来 (Summing up)

Through this guide I hope to demonstrate that you can take the same approaches that you may already be doing using Typescript, and port these ideas across to Python code bases. In particular, by augmenting Python’s duck-typing by implementing interfaces, explicitly through the use of abstract base classes, or more preferably through implicit interface implementation using the new Protocol feature introduced in Python 3.8.

通过本指南,我希望证明您可以采用与使用Typescript可能已经采用的相同方法,并将这些想法移植到Python代码库中。 尤其是,通过使用abstract base classes显式地实现接口来增强Python的鸭子类型,或者更可取的是,使用Python 3.8中引入的新Protocol功能通过隐式接口实现来实现。

If you have any questions related to this guide please feel free to leave them below or message me directly.

如果您对本指南有任何疑问,请随时将其留在下面或直接给我发消息。

翻译自: https://medium.com/swlh/field-guide-to-advanced-python-types-for-typescript-developers-d53ccb6f3a31

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值