python 学习指南_Python类型检查终极指南

本文是关于Python类型检查的全面指南,介绍了Python的动态和静态类型系统、鸭子类型、类型注释以及如何使用Mypy进行静态类型检查。通过示例,解释了如何在代码中添加类型提示,以及如何利用类型检查来发现潜在错误,同时探讨了类型检查的优缺点和使用场景。
摘要由CSDN通过智能技术生成

python 学习指南

In this guide, you will get a look into Python type checking. Traditionally, types have been handled by the Python interpreter in a flexible but implicit way. Recent versions of Python allow you to specify explicit type hints that can be used by different tools to help you develop your code more efficiently.

在本指南中,您将了解Python类型检查。 传统上,类型由Python解释器以灵活但隐式的方式处理。 Python的最新版本允许您指定显式类型提示,不同的工具可以使用这些类型提示来帮助您更有效地开发代码。

In this tutorial, you’ll learn about the following:

在本教程中,您将了解以下内容:

  • Type annotations and type hints
  • Adding static types to code, both your code and the code of others
  • Running a static type checker
  • Enforcing types at runtime
  • 类型注释和类型提示
  • 在代码中添加静态类型,包括您的代码和其他代码
  • 运行静态类型检查器
  • 在运行时强制类型

This is a comprehensive guide that will cover a lot of ground. If you want to just get a quick glimpse of how type hints work in Python, and see whether type checking is something you would include in your code, you don’t need to read all of it. The two sections Hello Types and Pros and Cons will give you a taste of how type checking works and recommendations about when it’ll be useful.

这是一本全面的指南,内容涉及很多领域。 如果您想快速了解Python中类型提示的工作原理,并查看是否将类型检查包含在代码中,则无需阅读所有内容。 Hello TypesPros and Cons这两个部分将带您领略类型检查的工作原理以及有关何时使用的建议。

Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level.

免费奖金: 关于Python精通的5个想法 ,这是针对Python开发人员的免费课程,向您展示了将Python技能提升到新水平所需的路线图和心态。

类型系统 (Type Systems)

All programming languages include some kind of type system that formalizes which categories of objects it can work with and how those categories are treated. For instance, a type system can define a numerical type, with 42 as one example of an object of numerical type.

所有编程语言都包括某种类型系统 ,该系统形式化了可以使用的对象类别以及如何对待这些类别。 例如,类型系统可以定义数字类型,其中42是数字类型对象的一个​​示例。

动态打字 (Dynamic Typing)

Python is a dynamically typed language. This means that the Python interpreter does type checking only as code runs, and that the type of a variable is allowed to change over its lifetime. The following dummy examples demonstrate that Python has dynamic typing:

Python是一种动态类型的语言。 这意味着Python解释器仅在代码运行时才进行类型检查,并且允许变量的类型在其生命周期内进行更改。 以下虚拟示例演示Python具有动态类型:

>>>
>>> if False:
...     1 + "two"  # This line never runs, so no TypeError is raised
... else:
...     1 + 2
...
3

>>> 1 + "two"  # Now this is type checked, and a TypeError is raised
TypeError: unsupported operand type(s) for +: 'int' and 'str'

>>>

In the first example, the branch 1 + "two" never runs so it’s never type checked. The second example shows that when 1 + "two" is evaluated it raises a TypeError since you can’t add an integer and a string in Python.

在第一个示例中,分支1 + "two"从不运行,因此永远不会经过类型检查。 第二个示例显示,当对1 + "two"求值时,它将引发TypeError因为您无法在Python中添加整数和字符串。

Next, let’s see if variables can change type:

接下来,让我们看看变量是否可以更改类型:

>>>
>>> thing = "Hello"
>>> type(thing)
<class 'str'>

>>> thing = 28.1
>>> type(thing)
<class 'float'>

>>>

type() returns the type of an object. These examples confirm that the type of thing is allowed to change, and Python correctly infers the type as it changes.

type()返回对象的类型。 这些示例确认thing的类型被允许更改,并且Python在更改类型时正确地推断出该类型。

静态打字 (Static Typing)

The opposite of dynamic typing is static typing. Static type checks are performed without running the program. In most statically typed languages, for instance C and Java, this is done as your program is compiled.

动态类型化的反面是静态类型化。 在不运行程序的情况下执行静态类型检查。 在大多数静态类型的语言中,例如C和Java,这是在编译程序时完成的。

With static typing, variables generally are not allowed to change types, although mechanisms for casting a variable to a different type may exist.

对于静态类型,尽管可能存在将变量转换为其他类型的机制,但是通常不允许变量更改类型。

Let’s look at a quick example from a statically typed language. Consider the following Java snippet:

让我们看一个来自静态类型语言的简单示例。 考虑以下Java代码段:

 String String thingthing ;
;
thing thing = = "Hello""Hello" ;
;

The first line declares that the variable name thing is bound to the String type at compile time. The name can never be rebound to another type. In the second line, thing is assigned a value. It can never be assigned a value that is not a String object. For instance, if you were to later say thing = 28.1f the compiler would raise an error because of incompatible types.

第一行声明在编译时将变量名thing绑定到String类型。 这个名字永远不会反弹到另一种类型。 在第二行中,为thing分配了一个值。 永远不能为它分配一个非String对象的值。 例如,如果您稍后要说thing = 28.1f则编译器会由于类型不兼容而引发错误。

Python will always remain a dynamically typed language. However, PEP 484 introduced type hints, which make it possible to also do static type checking of Python code.

Python将始终是一种动态类型化的语言 。 但是, PEP 484引入了类型提示,这使得还可以对Python代码进行静态类型检查。

Unlike how types work in most other statically typed languages, type hints by themselves don’t cause Python to enforce types. As the name says, type hints just suggest types. There are other tools, which you’ll see later, that perform static type checking using type hints.

与大多数其他静态类型语言中的类型工作方式不同,类型提示本身不会导致Python强制执行类型。 顾名思义,类型提示只是建议类型。 还有其他工具, 您将在以后看到 ,它们使用类型提示执行静态类型检查。

鸭打字 (Duck Typing)

Another term that is often used when talking about Python is duck typing. This moniker comes from the phrase “if it walks like a duck and it quacks like a duck, then it must be a duck” (or any of its variations).

在谈论Python时经常使用的另一个术语是鸭子类型 。 这个绰号来自“如果它像鸭子一样走路,而像鸭子一样嘎嘎叫,那么它一定是鸭子”(或其任何变体 )。

Duck typing is a concept related to dynamic typing, where the type or the class of an object is less important than the methods it defines. Using duck typing you do not check types at all. Instead you check for the presence of a given method or attribute.

鸭子类型是与动态类型相关的概念,其中对象的类型或类不如其定义的方法重要。 使用鸭子类型您根本不需要检查类型。 而是检查给定方法或属性的存在。

As an example, you can call len() on any Python object that defines a .__len__() method:

例如,您可以在定义.__len__()方法的任何Python对象上调用len()

>>>
>>>
 >>>  class TheHobbit :
...     def __len__ ( self ):
...         return 95022
...
>>>  the_hobbit = TheHobbit ()
>>>  len ( the_hobbit )
95022

Note that the call to len() gives the return value of the .__len__() method. In fact, the implementation of len() is essentially equivalent to the following:

请注意,对len()的调用给出了.__len__()方法的返回值。 实际上, len()的实现基本上等同于以下内容:

In order to call len(obj), the only real constraint on obj is that it must define a .__len__() method. Otherwise, the object can be of types as different as str, list, dict, or TheHobbit.

为了调用len(obj) ,对obj的唯一真正限制是它必须定义.__len__()方法。 否则,对象的类型可以与strlistdictTheHobbit

Duck typing is somewhat supported when doing static type checking of Python code, using structural subtyping. You’ll learn more about duck typing later.

使用结构子类型对Python代码进行静态类型检查时,在某种程度上支持鸭子类型 。 稍后,您将了解有关鸭类打字的更多信息

你好类型 (Hello Types)

In this section you’ll see how to add type hints to a function. The following function turns a text string into a headline by adding proper capitalization and a decorative line:

在本节中,您将看到如何向函数添加类型提示。 以下函数通过添加适当的大写字母和装饰线将文本字符串转换为标题:

 def def headlineheadline (( texttext , , alignalign == TrueTrue ):
    ):
    if if alignalign :
        :
        return return ff "{text.title()}"{text.title()} nn {'-' * len(text)}"
    {'-' * len(text)}"
    elseelse :
        :
        return return ff " {text.title()} "" {text.title()} " .. centercenter (( 5050 , , "o""o" )
)

By default the function returns the headline left aligned with an underline. By setting the align flag to False you can alternatively have the headline be centered with a surrounding line of o:

默认情况下,该函数返回与下划线对齐的左侧标题。 通过将align标志设置为False您可以使标题以o的环绕线为中心:

>>>
>>>
 >>>  print ( headline ( "python type checking" ))
Python Type Checking
--------------------

>>>  print ( headline ( "python type checking" , align = False ))
oooooooooooooo Python Type Checking oooooooooooooo

It’s time for our first type hints! To add information about types to the function, you simply annotate its arguments and return value as follows:

现在是我们第一类提示的时候了! 要将有关类型的信息添加到函数,只需注释其参数并返回值,如下所示:

The text: str syntax says that the text argument should be of type str. Similarly, the optional align argument should have type bool with the default value True. Finally, the -> str notation specifies that headline() will return a string.

text: str语法表示text参数应为str类型。 同样,可选的align参数的类型应该为bool ,默认值为True 。 最后, -> str表示法指定headline()将返回一个字符串。

In terms of style, PEP 8 recommends the following:

样式方面PEP 8建议以下内容:

  • Use normal rules for colons, that is, no space before and one space after a colon: text: str.
  • Use spaces around the = sign when combining an argument annotation with a default value: align: bool = True.
  • Use spaces around the -> arrow: def headline(...) -> str.
  • 对冒号使用正常规则,即冒号前没有空格,冒号后没有空格: text: str
  • 将参数注释与默认值组合时,请在=号周围使用空格: align: bool = True
  • ->箭头周围使用空格: def headline(...) -> str

Adding type hints like this has no runtime effect: they are only hints and are not enforced on their own. For instance, if we use a wrong type for the (admittedly badly named) align argument, the code still runs without any problems or warnings:

像这样添加类型提示不会对运行时间产生影响:它们只是提示,不能单独执行。 例如,如果我们对align参数使用了错误的类型(公认的错误命名),那么代码仍然可以正常运行,没有任何问题或警告:

>>>
>>> print(headline("python type checking", align="left"))
Python Type Checking
--------------------

>>>

Note: The reason this seemingly works is that the string "left" compares as truthy. Using align="center" would not have the desired effect as "center" is also truthy.

注意:看来有效的原因是字符串"left" 比较为true 。 使用align="center"不会产生预期的效果,因为"center"也是正确的。

To catch this kind of error you can use a static type checker. That is, a tool that checks the types of your code without actually running it in the traditional sense.

要捕获此类错误,可以使用静态类型检查器。 也就是说,该工具可以检查代码的类型,而无需实际运行传统意义上的代码。

You might already have such a type checker built into your editor. For instance PyCharm immediately gives you a warning:

您可能已经在编辑器中内置了这种类型检查器。 例如, PyCharm立即向您发出警告:

PyCharm flagging a type error

The most common tool for doing type checking is Mypy though. You’ll get a short introduction to Mypy in a moment, while you can learn much more about how it works later.

不过,进行类型检查的最常用工具是Mypy 。 稍后,您将对Mypy进行简短介绍,同时稍后可以了解更多有关Mypy的工作原理。

If you don’t already have Mypy on your system, you can install it using pip:

如果您的系统上还没有Mypy,则可以使用pip进行安装:

 $ pip install mypy
$ pip install mypy

Put the following code in a file called headlines.py:

将以下代码放在一个名为headlines.py的文件中:

This is essentially the same code you saw earlier: the definition of headline() and two examples that are using it.

这基本上与您之前看到的代码相同: headline()的定义和两个使用它的示例。

Now run Mypy on this code:

现在在以下代码上运行Mypy:

 $ mypy headlines.py
$ mypy headlines.py
headlines.py:10: error: Argument "align" to "headline" has incompatible
headlines.py:10: error: Argument "align" to "headline" has incompatible
                        type "str"; expected "bool"
                        type "str"; expected "bool"

Based on the type hints, Mypy is able to tell us that we are using the wrong type on line 10.

根据类型提示,Mypy可以告诉我们第10行使用的类型错误。

To fix the issue in the code you should change the value of the align argument you are passing in. You might also rename the align flag to something less confusing:

要解决代码中的问题,您应该更改传入的align参数的值。您还可以将align标志重命名为不太混乱的名称:

Here you’ve changed align to centered, and correctly used a boolean value for centered when calling headline(). The code now passes Mypy:

在这里,您已将align更改为centered ,并在调用headline()时正确使用了一个布尔值来centered 。 现在,代码通过了Mypy:

 $ mypy headlines.py
$ mypy headlines.py
$ 
$ 

No output from Mypy means that no type errors were detected. Furthermore, when you run the code you see the expected output:

Mypy没有输出意味着没有检测到类型错误。 此外,当您运行代码时,您会看到预期的输出:

The first headline is aligned to the left, while the second one is centered.

第一个标题向左对齐,第二个标题居中。

利弊 (Pros and Cons)

The previous section gave you a little taste of what type checking in Python looks like. You also saw an example of one of the advantages of adding types to your code: type hints help catch certain errors. Other advantages include:

上一节向您介绍了Python中的类型检查的外观。 您还看到了一个向代码添加类型的优点之一的示例:类型提示有助于捕获某些错误 。 其他优点包括:

  • Type hints help document your code. Traditionally, you would use docstrings if you wanted to document the expected types of a function’s arguments. This works, but as there is no standard for docstrings (despite PEP 257 they can’t be easily used for automatic checks.

  • Type hints improve IDEs and linters. They make it much easier to statically reason about your code. This in turn allows IDEs to offer better code completion and similar features. With the type annotation, PyCharm knows that text is a string, and can give specific suggestions based on this:

    Code completion in PyCharm on a typed variable

  • Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program. While the dynamic nature of Python is one of its great assets, being conscious about relying on duck typing, overloaded methods, or multiple return types is a good thing.

  • 输入提示可帮助您记录代码 。 传统上,如果要记录函数参数的预期类型,则应使用docstrings 。 这是可行的,但是由于没有文档字符串标准(尽管有PEP 257,它们不能轻易用于自动检查)。

  • 类型提示可改善IDE和linters 。 它们使静态推理代码变得容易得多。 反过来,这使IDE可以提供更好的代码完成和类似功能。 通过类型注释,PyCharm知道text是字符串,并可以基于此给出具体建议:

  • 类型提示可帮助您构建和维护更简洁的体系结构 。 类型提示的行为迫使您考虑程序中的类型。 尽管Python的动态特性是其强大的资产之一,但是意识到依赖于鸭子类型,重载方法或多种返回类型是一件好事。

Of course, static type checking is not all peaches and cream. There are also some downsides you should consider:

当然,静态类型检查并非全部都是桃子和奶油。 您还应考虑以下缺点:

  • Type hints take developer time and effort to add. Even though it probably pays off in spending less time debugging, you will spend more time entering code.

  • Type hints work best in modern Pythons. Annotations were introduced in Python 3.0, and it’s possible to use type comments in Python 2.7. Still, improvements like variable annotations and postponed evaluation of type hints mean that you’ll have a better experience doing type checks using Python 3.6 or even Python 3.7.

  • Type hints introduce a slight penalty in start-up time. If you need to use the typing module the import time may be significant, especially in short scripts.

  • 类型提示需要开发人员花费时间和精力进行添加 。 即使花费较少的调试时间可能会有所回报,但是您将花费更多的时间输入代码。

  • 类型提示在现代Python中效果最好 。 注释是在Python 3.0中引入的,并且可以在Python 2.7中使用类型注释 。 尽管如此,诸如变量注释类型提示的延迟评估之类的改进仍意味着您将拥有使用Python 3.6甚至Python 3.7进行类型检查的更好体验。

  • 类型提示会对启动时间造成轻微的影响 。 如果需要使用typing模块,则导入时间可能很长,尤其是在短脚本中。

You’ll later learn about the typing module, and how it’s necessary in most cases when you add type hints. Importing modules necessarily take some time, but how much?

稍后,您将了解typing模块,以及在大多数情况下添加类型提示时的必要性。 导入模块一定要花一些时间,但是要多少呢?

To get some idea about this, create two files: empty_file.py should be an empty file, while import_typing.py should contain the following line:

为了对此有所了解,请创建两个文件: empty_file.py应该是一个空文件,而import_typing.py应该包含以下行:

 import import typing
typing

On Linux it’s quite easy to check how much time the typing import takes using the perf utility:

在Linux上,使用perf实用程序很容易检查typing需要多少时间:

So running the import typing.py script takes about 45 milliseconds. Of course this is not all time spent on importing typing. Some of this is overhead in starting the Python interpreter, so let’s compare to running Python on an empty file:

因此,运行import typing.py脚本大约需要45毫秒。 当然,这并不是花所有时间在typing 。 其中一些是启动Python解释器的开销,因此让我们与在空文件上运行Python进行比较:

 $ perf stat -r $ perf stat -r 1000 python3.6 empty_file.py

1000 python3.6 empty_file.py

 Performance counter stats for 'python3.6 empty_file.py' (1000 runs):

 Performance counter stats for 'python3.6 empty_file.py' (1000 runs):

 [ ... extra information hidden for brevity ... ]

 [ ... extra information hidden for brevity ... ]

       0.028077845 seconds time elapsed    ( +-  0.49% )
       0.028077845 seconds time elapsed    ( +-  0.49% )

Based on this test, the import of the typing module takes around 17 milliseconds on Python 3.6.

根据此测试,在Python 3.6上, typing模块的导入大约需要17毫秒。

One of the advertised improvements in Python 3.7 is faster startup. Let’s see if the results are different:

Python 3.7中宣传的一项改进是启动速度更快 。 让我们看看结果是否不同:

Indeed, the general startup time is reduced by about 8 milliseconds, and the time to import typing is down from 17 to around 6 milliseconds—almost 3 times faster.

实际上,一般的启动时间减少了大约8毫秒,并且导入typing的时间从17减少到了大约6毫秒-快了将近三倍。

Using timeit

使用timeit

There are similar tools on other platforms. Python itself comes with the timeit module in the standard library. Typically, we would directly use timeit for the timings above. However, timeit struggles to time imports reliably because Python is clever about importing modules only once. Consider the following example:

在其他平台上也有类似的工具。 Python本身带有标准库中的timeit模块。 通常,对于上述时间,我们将直接使用timeit 。 但是, timeit难以可靠地对导入进行计时,因为Python聪明地只将模块导入一次。 考虑以下示例:

 $ python3.6 -m timeit $ python3.6 -m timeit "import typing"
"import typing"
10000000 loops, best of 3: 0.134 usec per loop
10000000 loops, best of 3: 0.134 usec per loop

While you get a result, you should be suspicious about the result: 0.1 microsecond is more than 100000 times faster than what perf measured! What timeit has actually done is to run the import typing statement 30 million times, with Python actually only importing typing once.

当你得到一个结果,你应该是可疑的结果:0.1微秒是比快超过10倍perf测! 什么timeit实际上做的是运行import typing声明3000万次,与Python实际上只是进口typing一次。

To get reasonable results you can tell timeit to only run once:

为了获得合理的结果,您可以告诉timeit只运行一次:

These results are on the same scale as the results from perf above. However, since these are based on only one execution of the code, they are not as reliable as those based on multiple runs.

这些结果与上述perf的结果具有相同的规模。 但是,由于这些仅基于代码的一次执行,因此它们不如基于多次运行的代码可靠。

The conclusion in both these cases is that importing typing takes a few milliseconds. For the majority of programs and scripts you write this will probably not be an issue.

在这两种情况下的结论是,导入typing需要花费几毫秒的时间。 对于您编写的大多数程序和脚本,这可能不会成为问题。

The New importtime Option

新的importtime选项

In Python 3.7 there is also a new command line option that can be used to figure out how much time imports take. Using -X importtime you’ll get a report about all imports that are made:

在Python 3.7中,还有一个新的命令行选项可用于确定导入需要花费多少时间。 使用-X importtime您将获得有关所有已完成导入的报告:

 $ python3.7 -X importtime import_typing.py
$ python3.7 -X importtime import_typing.py
import time: self [us] | cumulative | imported package
import time: self [us] | cumulative | imported package
[ ... some information hidden for brevity ... ]
[ ... some information hidden for brevity ... ]
import time:       358 |        358 | zipimport
import time:       358 |        358 | zipimport
import time:      2107 |      14610 | site
import time:      2107 |      14610 | site
import time:       272 |        272 |   collections.abc
import time:       272 |        272 |   collections.abc
import time:       664 |       3058 |   re
import time:       664 |       3058 |   re
import time:      3044 |       6373 | typing
import time:      3044 |       6373 | typing

This shows a similar result. Importing typing takes about 6 milliseconds. If you’ll read the report closely you can notice that around half of this time is spent on importing the collections.abc and re modules which typing depends on.

这显示了相似的结果。 导入typing大约需要6毫秒。 如果您仔细阅读该报告,您会注意到大约有一半的时间用于导入collections.abcre typing依赖的模块。

So, should you use static type checking in your own code? Well, it’s not an all-or-nothing question. Luckily, Python supports the concept of gradual typing. This means that you can gradually introduce types into your code. Code without type hints will be ignored by the static type checker. Therefore, you can start adding types to critical components, and continue as long as it adds value to you.

因此,您应该在自己的代码中使用静态类型检查吗? 好吧,这不是一个全有或全无的问题。 幸运的是,Python支持逐步类型化的概念。 这意味着您可以逐渐将类型引入代码中。 没有类型提示的代码将被静态类型检查器忽略。 因此,您可以开始向关键组件添加类型,并继续添加,只要它能为您增加价值即可。

Looking at the lists above of pros and cons you’ll notice that adding types will have no effect on your running program or the users of your program. Type checking is meant to make your life as a developer better and more convenient.

查看上面的利弊列表,您会发现添加类型对正在运行的程序或程序用户没有影响。 类型检查旨在使您作为开发人员的生活更美好,更便捷。

A few rules of thumb on whether to add types to your project are:

关于是否向项目中添加类型的一些经验法则是:

  • If you are just beginning to learn Python, you can safely wait with type hints until you have more experience.

  • Type hints add little value in short throw-away scripts.

  • In libraries that will be used by others, especially ones published on PyPI, type hints add a lot of value. Other code using your libraries need these type hints to be properly type checked itself. For examples of projects using type hints see cursive_re, black, our own Real Python Reader, and Mypy itself.

  • In bigger projects, type hints help you understand how types flow through your code, and are highly recommended. Even more so in projects where you cooperate with others.

  • 如果您刚刚开始学习Python,则可以放心使用类型提示,直到您有更多经验为止。

  • 简短的一次性脚本中,类型提示几乎没有任何价值。

  • 在其他人会使用的库中,尤其是在PyPI上发布的库中,类型提示会增加很多价值。 使用您的库的其他代码需要这些类型提示才能正确进行类型检查。 有关使用类型提示的项目的示例,请参见cursive_reblack ,我们自己的Real Python ReaderMypy本身。

  • 在较大的项目中,类型提示可帮助您了解类型如何在代码中流动,因此强烈建议使用。 在与他人合作的项目中更是如此。

In his excellent article The State of Type Hints in Python Bernát Gábor recommends that “type hints should be used whenever unit tests are worth writing.” Indeed, type hints play a similar role as tests in your code: they help you as a developer write better code.

BernátGábor在他的出色文章《 Python中的类型提示的状态》中建议“ 只要值得编写单元测试,就应使用类型提示 。” 实际上,类型提示在代码中的作用与测试相似:它们可以帮助您作为开发人员编写更好的代码。

Hopefully you now have an idea about how type checking works in Python and whether it’s something you would like to employ in your own projects.

希望您现在对Python中的类型检查如何工作以及是否要在自己的项目中使用它有所了解。

In the rest of this guide, we’ll go into more detail about the Python type system, including how you run static type checkers (with particular focus on Mypy), how you type check code that uses libraries without type hints, and how you use annotations at runtime.

在本指南的其余部分,我们将详细介绍Python类型系统,包括如何运行静态类型检查器(特别关注Mypy),如何键入使用不带类型提示的库的检查代码以及如何运行。在运行时使用注释。

注解 (Annotations)

Annotations were introduced in Python 3.0, originally without any specific purpose. They were simply a way to associate arbitrary expressions to function arguments and return values.

注释是在Python 3.0引入的 ,最初没有任何特定目的。 它们只是将任意表达式与函数参数和返回值关联的一种方式。

Years later, PEP 484 defined how to add type hints to your Python code, based off work that Jukka Lehtosalo had done on his Ph.D. project—Mypy. The main way to add type hints is using annotations. As type checking is becoming more and more common, this also means that annotations should mainly be reserved for type hints.

多年后, PEP 484根据Jukka Lehtosalo在其博士学位上所做的工作,定义了如何在您的Python代码中添加类型提示。 项目-Mypy。 添加类型提示的主要方法是使用注释。 随着类型检查变得越来越普遍,这也意味着注释应主要保留给类型提示。

The next sections explain how annotations work in the context of type hints.

下一节将说明注释在类型提示的上下文中如何工作。

功能注释 (Function Annotations)

For functions, you can annotate arguments and the return value. This is done as follows:

对于函数,您可以注释参数和返回值。 这样做如下:

For arguments the syntax is argument: annotation, while the return type is annotated using -> annotation. Note that the annotation must be a valid Python expression.

对于参数,语法为argument: annotation ,而返回类型使用-> annotation 。 请注意,注释必须是有效的Python表达式。

The following simple example adds annotations to a function that calculates the circumference of a circle:

下面的简单示例将注释添加到计算圆的周长的函数中:

 import import math

math

def def circumferencecircumference (( radiusradius : : floatfloat ) ) -> -> floatfloat :
    :
    return return 2 2 * * mathmath .. pi pi * * radius
radius

When running the code, you can also inspect the annotations. They are stored in a special .__annotations__ attribute on the function:

运行代码时,您也可以检查注释。 它们存储在函数的特殊.__annotations__属性中:

>>>
>>>
 >>>  circumference ( 1.23 )
7.728317927830891

>>>  circumference . __annotations__
{'radius': <class 'float'>, 'return': <class 'float'>}

Sometimes you might be confused by how Mypy is interpreting your type hints. For those cases there are special Mypy expressions: reveal_type() and reveal_locals(). You can add these to your code before running Mypy, and Mypy will dutifully report which types it has inferred. As an example, save the following code to reveal.py:

有时,您可能会对Mypy如何解释您的类型提示感到困惑。 对于这些情况,有一些特殊的Mypy表达式: reveal_type()reveal_locals() 。 您可以在运行Mypy之前将它们添加到代码中,然后Mypy会尽职报告其推断出的​​类型。 作为示例,将以下代码保存到reveal.py

Next, run this code through Mypy:

接下来,通过Mypy运行以下代码:

 $ mypy reveal.py
$ mypy reveal.py
reveal.py:4: error: Revealed type is 'builtins.float'

reveal.py:4: error: Revealed type is 'builtins.float'

reveal.py:8: error: Revealed local types are:
reveal.py:8: error: Revealed local types are:
reveal.py:8: error: circumference: builtins.float
reveal.py:8: error: circumference: builtins.float
reveal.py:8: error: radius: builtins.int
reveal.py:8: error: radius: builtins.int

Even without any annotations Mypy has correctly inferred the types of the built-in math.pi, as well as our local variables radius and circumference.

即使没有任何注释,Mypy也可以正确推断出内置math.pi的类型,以及我们的局部变量radiuscircumference

Note: The reveal expressions are only meant as a tool helping you add types and debug your type hints. If you try to run the reveal.py file as a Python script it will crash with a NameError since reveal_type() is not a function known to the Python interpreter.

注意:揭示表达式仅是帮助您添加类型和调试类型提示的工具。 如果您尝试以Python脚本的reveal.py运行reveal.py文件,它将由于NameError崩溃,因为reveal_type()不是Python解释器已知的函数。

If Mypy says that “Name ‘reveal_locals‘ is not defined” you might need to update your Mypy installation. The reveal_locals() expression is available in Mypy version 0.610 and later.

如果Mypy说“名称' reveal_locals '未定义”,则可能需要更新Mypy安装。 reveal_locals() 0.610及更高版本中提供了reveal_locals()表达式。

可变注释 (Variable Annotations)

In the definition of circumference() in the previous section, you only annotated the arguments and the return value. You did not add any annotations inside the function body. More often than not, this is enough.

在上一节的circumference()的定义中,您仅注释了参数和返回值。 您没有在函数体内添加任何注释。 通常,这足够了。

However, sometimes the type checker needs help in figuring out the types of variables as well. Variable annotations were defined in PEP 526 and introduced in Python 3.6. The syntax is the same as for function argument annotations:

但是,有时类型检查器在确定变量类型时也需要帮助。 变量注释在PEP 526中定义,并在Python 3.6中引入。 语法与函数参数注释的语法相同:

The variable pi has been annotated with the float type hint.

变量pi已使用float类型提示进行注释。

Note: Static type checkers are more than able to figure out that 3.142 is a float, so in this example the annotation of pi is not necessary. As you learn more about the Python type system, you’ll see more relevant examples of variable annotations.

注意:静态类型检查器不仅仅能够确定3.142是浮点数,因此在此示例中, pi的注释不是必需的。 当您了解有关Python类型系统的更多信息时,您将看到更多相关的变量注释示例。

Annotations of variables are stored in the module level __annotations__ dictionary:

变量的注释存储在模块级别的__annotations__字典中:

>>>
>>> circumference(1)
6.284

>>> __annotations__
{'pi': <class 'float'>}

>>>

You’re allowed to annotate a variable without giving it a value. This adds the annotation to the __annotations__ dictionary, while the variable remains undefined:

您可以在不给变量值的情况下对其进行注释。 这会将注释添加到__annotations__字典中,而变量仍未定义:

>>>
>>> nothing: str
>>> nothing
NameError: name 'nothing' is not defined

>>> __annotations__
{'nothing': <class 'str'>}

>>>

Since no value was assigned to nothing, the name nothing is not yet defined.

由于未将值赋给nothing ,因此尚未定义nothing

类型注释 (Type Comments)

As mentioned, annotations were introduced in Python 3, and they’ve not been backported to Python 2. This means that if you’re writing code that needs to support legacy Python, you can’t use annotations.

如前所述,注释是在Python 3中引入的,并且尚未向后移植到Python2。这意味着,如果要编写需要支持旧版Python的代码,则不能使用注释。

Instead, you can use type comments. These are specially formatted comments that can be used to add type hints compatible with older code. To add type comments to a function you do something like this:

相反,您可以使用类型注释。 这些是特殊格式的注释,可用于添加与旧代码兼容的类型提示。 要将类型注释添加到函数,请执行以下操作:

 import import math

math

def def circumferencecircumference (( radiusradius ):
):
# type: (float) -> float
# type: (float) -> float
return return 2 2 * * mathmath .. pi pi * * radius
radius

The type comments are just comments, so they can be used in any version of Python.

类型注释只是注释,因此可以在任何版本的Python中使用。

Type comments are handled directly by the type checker, so these types are not available in the __annotations__ dictionary:

类型注释由类型检查器直接处理,因此这些类型在__annotations__词典中不可用:

>>>
>>>
 >>>  circumference . __annotations__
{}

A type comment must start with the type: literal, and be on the same or the following line as the function definition. If you want to annotate a function with several arguments, you write each type separated by comma:

类型注释必须以type:文字开头,并且与函数定义在同一行或下一行。 如果要用多个参数注释一个函数,请编写每个用逗号分隔的类型:

You are also allowed to write each argument on a separate line with its own annotation:

您还可以将每个参数用自己的注释写在单独的行上:

 # headlines.py

# headlines.py

def def headlineheadline (
    (
    texttext ,           ,           # type: str
    # type: str
    widthwidth == 8080 ,       ,       # type: int
    # type: int
    fill_charfill_char == "-""-" ,  ,  # type: str
# type: str
):                  ):                  # type: (...) -> str
    # type: (...) -> str
    return return ff " {text.title()} "" {text.title()} " .. centercenter (( widthwidth , , fill_charfill_char )

)

printprint (( headlineheadline (( "type comments work""type comments work" , , widthwidth == 4040 ))
))

Run the example through Python and Mypy:

通过Python和Mypy运行示例:

If you have errors, for instance if you happened to call headline() with width="full" on line 10, Mypy will tell you:

如果您遇到错误,例如,如果您碰巧在第10行调用了headline() ,其width="full" ,Mypy会告诉您:

 $ mypy headline.py
$ mypy headline.py
headline.py:10: error: Argument "width" to "headline" has incompatible
headline.py:10: error: Argument "width" to "headline" has incompatible
                       type "str"; expected "int"
                       type "str"; expected "int"

You can also add type comments to variables. This is done similarly to how you add type comments to arguments:

您还可以将类型注释添加到变量。 类似于将类型注释添加到参数的方法:

In this example, pi will be type checked as a float variable.

在此示例中,将pi作为浮点变量进行类型检查。

那么,类型注释或类型注释呢? (So, Type Annotations or Type Comments?)

Should you use annotations or type comments when adding type hints to your own code? In short: Use annotations if you can, use type comments if you must.

将类型提示添加到自己的代码中时,应该使用注释还是键入注释? 简而言之: 如果可以,请使用注释,如果需要,请使用类型注释。

Annotations provide a cleaner syntax keeping type information closer to your code. They are also the officially recommended way of writing type hints, and will be further developed and properly maintained in the future.

注释提供了更简洁的语法,使类型信息更接近您的代码。 它们也是书写类型提示的官方推荐方式 ,并且将来会得到进一步开发和适当维护。

Type comments are more verbose and might conflict with other kinds of comments in your code like linter directives. However, they can be used in code bases that don’t support annotations.

类型注释更加冗长,并且可能与代码中的其他类型的注释(例如linter指令)冲突。 但是,它们可以在不支持注释的代码库中使用。

There is also hidden option number three: stub files. You will learn about these later, when we discuss adding types to third party libraries.

还有第三个隐藏的选项: 存根文件 。 稍后,当我们讨论将类型添加到第三方库时,您将学到这些。

Stub files will work in any version of Python, at the expense of having to maintain a second set of files. In general, you only want to use stub files if you can’t change the original source code.

存根文件可在任何版本的Python中工作,但必须维护第二组文件。 通常,仅在无法更改原始源代码的情况下才想使用存根文件。

玩Python类型,第1部分 (Playing With Python Types, Part 1)

Up until now you’ve only used basic types like str, float, and bool in your type hints. The Python type system is quite powerful, and supports many kinds of more complex types. This is necessary as it needs to be able to reasonably model Python’s dynamic duck typing nature.

到目前为止,您仅在类型提示中使用了strfloatbool类的基本类型。 Python类型系统功能强大,并支持许多更复杂的类型。 这是必要的,因为它需要能够合理地建模Python的动态鸭子类型化性质。

In this section you will learn more about this type system, while implementing a simple card game. You will see how to specify:

在本节中,您将学习有关这种类型的系统的更多信息,同时实现一个简单的纸牌游戏。 您将看到如何指定:

After a short detour into some type theory you will then see even more ways to specify types in Python. You can find the code examples from this section here.

简短地绕过一些类型理论之后,您将看到更多在Python中指定类型的方法 。 你可以找到本节中的代码示例在这里

示例:一副纸牌 (Example: A Deck of Cards)

The following example shows an implementation of a regular (French) deck of cards:

以下示例显示了常规(法语)纸牌的实现

 # game.py

# game.py

import import random

random

SUITS SUITS = = "♠ ♡ ♢ ♣""♠ ♡ ♢ ♣" .. splitsplit ()
()
RANKS RANKS = = "2 3 4 5 6 7 8 9 10 J Q K A""2 3 4 5 6 7 8 9 10 J Q K A" .. splitsplit ()

()

def def create_deckcreate_deck (( shuffleshuffle == FalseFalse ):
    ):
    """Create a new deck of 52 cards"""
    """Create a new deck of 52 cards"""
    deck deck = = [([( ss , , rr ) ) for for r r in in RANKS RANKS for for s s in in SUITSSUITS ]
    ]
    if if shuffleshuffle :
        :
        randomrandom .. shuffleshuffle (( deckdeck )
    )
    return return deck

deck

def def deal_handsdeal_hands (( deckdeck ):
    ):
    """Deal the cards in the deck into four hands"""
    """Deal the cards in the deck into four hands"""
    return return (( deckdeck [[ 00 :::: 44 ], ], deckdeck [[ 11 :::: 44 ], ], deckdeck [[ 22 :::: 44 ], ], deckdeck [[ 33 :::: 44 ])

])

def def playplay ():
    ():
    """Play a 4-player card game"""
    """Play a 4-player card game"""
    deck deck = = create_deckcreate_deck (( shuffleshuffle == TrueTrue )
    )
    names names = = "P1 P2 P3 P4""P1 P2 P3 P4" .. splitsplit ()
    ()
    hands hands = = {{ nn : : h h for for nn , , h h in in zipzip (( namesnames , , deal_handsdeal_hands (( deckdeck ))}

    ))}

    for for namename , , cards cards in in handshands .. itemsitems ():
        ():
        card_str card_str = = " "" " .. joinjoin (( ff "" {s}{r}{s}{r} " " for for (( ss , , rr ) ) in in cardscards )
        )
        printprint (( ff "" {name}{name} : :  {card_str}{card_str} "" )

)

if if __name__ __name__ == == "__main__""__main__" :
    :
    playplay ()
()

Each card is represented as a tuple of strings denoting the suit and rank. The deck is represented as a list of cards. create_deck() creates a regular deck of 52 playing cards, and optionally shuffles the cards. deal_hands() deals the deck of cards to four players.

每张卡都由表示西装和等级的字符串元组表示。 牌组表示为卡片列表。 create_deck()创建一个由52张扑克牌组成的常规牌组,并有选择地随机播放纸牌。 deal_hands()deal_hands()纸牌发给四个玩家。

Finally, play() plays the game. As of now, it only prepares for a card game by constructing a shuffled deck and dealing cards to each player. The following is a typical output:

最后, play()玩游戏。 到目前为止,它只是通过构造一个洗牌后的纸牌并向每个玩家分发纸牌来为纸牌游戏做准备。 以下是典型的输出:

You will see how to extend this example into a more interesting game as we move along.

随着我们的前进,您将看到如何将此示例扩展为更有趣的游戏。

序列和映射 (Sequences and Mappings)

Let’s add type hints to our card game. In other words, let’s annotate the functions create_deck(), deal_hands(), and play(). The first challenge is that you need to annotate composite types like the list used to represent the deck of cards and the tuples used to represent the cards themselves.

让我们在纸牌游戏中添加类型提示。 换句话说,让我们对函数create_deck()deal_hands()play()注释。 第一个挑战是您需要注释复合类型,例如用于表示纸牌组的列表和用于表示纸牌本身的元组。

With simple types like str, float, and bool, adding type hints is as easy as using the type itself:

使用strfloatbool等简单类型,添加类型提示与使用类型本身一样简单:

>>>
>>> name: str = "Guido"
>>> pi: float = 3.142
>>> centered: bool = False

>>>

With composite types, you are allowed to do the same:

对于复合类型,您可以执行以下操作:

>>>
>>> names: list = ["Guido", "Jukka", "Ivan"]
>>> version: tuple = (3, 7, 1)
>>> options: dict = {"centered": False, "capitalize": True}

>>>

However, this does not really tell the full story. What will be the types of names[2], version[0], and options["centered"]? In this concrete case you can see that they are str, int, and bool, respectively. However, the type hints themselves give no information about this.

但是,这并不能真正说明全部情况。 names[2]version[0]options["centered"]什么? 在这种具体情况下,您可以看到它们分别是strintbool 。 但是,类型提示本身不提供有关此信息。

Instead, you should use the special types defined in the typing module. These types add syntax for specifying the types of elements of composite types. You can write the following:

相反,你应该使用中定义的特殊类型的typing模块 。 这些类型添加了用于指定复合类型的元素类型的语法。 您可以编写以下内容:

>>>
>>> from typing import Dict, List, Tuple

>>> names: List[str] = ["Guido", "Jukka", "Ivan"]
>>> version: Tuple[int, int, int] = (3, 7, 1)
>>> options: Dict[str, bool] = {"centered": False, "capitalize": True}

>>>

Note that each of these types start with a capital letter and that they all use square brackets to define item types:

请注意,以下每种类型均以大写字母开头,并且都使用方括号定义项目类型:

  • names is a list of strings
  • version is a 3-tuple consisting of three integers
  • options is a dictionary mapping strings to boolean values
  • names是字符串列表
  • version是一个由三个整数组成的3元组
  • options是将字符串映射为布尔值的字典

The typing module contains many more composite types, including Counter, Deque, FrozenSet, NamedTuple, and Set. In addition, the module includes other kinds of types that you’ll see in later sections.

typing模块包含更多复合类型,包括CounterDequeFrozenSetNamedTupleSet 。 此外,该模块还包含其他类型的类型,您将在后面的部分中看到。

Let’s return to the card game. A card is represented by a tuple of two strings. You can write this as Tuple[str, str], so the type of the deck of cards becomes List[Tuple[str, str]]. Therefore you can annotate create_deck() as follows:

让我们回到纸牌游戏。 卡由两个字符串的元组表示。 您可以将其写为Tuple[str, str] ,因此卡片List[Tuple[str, str]]的类型为List[Tuple[str, str]] 。 因此,您可以如下注释create_deck()

 def def create_deckcreate_deck (( shuffleshuffle : : bool bool = = FalseFalse ) ) -> -> ListList [[ TupleTuple [[ strstr , , strstr ]]:
    ]]:
    """Create a new deck of 52 cards"""
    """Create a new deck of 52 cards"""
    deck deck = = [([( ss , , rr ) ) for for r r in in RANKS RANKS for for s s in in SUITSSUITS ]
    ]
    if if shuffleshuffle :
        :
        randomrandom .. shuffleshuffle (( deckdeck )
    )
    return return deck
deck

In addition to the return value, you’ve also added the bool type to the optional shuffle argument.

除了返回值之外,还向可选的shuffle参数中添加了bool类型。

Note: Tuples and lists are annotated differently.

注意:元组和列表的注释不同。

A tuple is an immutable sequence, and typically consists of a fixed number of possibly differently typed elements. For example, we represent a card as a tuple of suit and rank. In general, you write Tuple[t_1, t_2, ..., t_n] for an n-tuple.

元组是一个不变的序列,通常由固定数量的可能不同类型的元素组成。 例如,我们将卡表示为西装和等级的元组。 通常,您为n个元组编写Tuple[t_1, t_2, ..., t_n]

A list is a mutable sequence and usually consists of an unknown number of elements of the same type, for instance a list of cards. No matter how many elements are in the list there is only one type in the annotation: List[t].

列表是可变序列,通常由未知数量的相同类型的元素组成,例如卡片列表。 无论列表中有多少个元素,注释中只有一种类型: List[t]

In many cases your functions will expect some kind of sequence, and not really care whether it is a list or a tuple. In these cases you should use typing.Sequence when annotating the function argument:

在许多情况下,您的函数会期望某种顺序 ,而实际上并不关心它是列表还是元组。 在这些情况下,在对函数参数进行注释时应使用typing.Sequence

Using Sequence is an example of using duck typing. A Sequence is anything that supports len() and .__getitem__(), independent of its actual type.

使用Sequence是使用鸭子输入的示例。 Sequence是支持len().__getitem__()任何事物,而与它的实际类型无关。

类型别名 (Type Aliases)

The type hints might become quite oblique when working with nested types like the deck of cards. You may need to stare at List[Tuple[str, str]] a bit before figuring out that it matches our representation of a deck of cards.

当使用嵌套类型(如纸牌组)时,类型提示可能会变得非常倾斜。 您可能需要先凝视List[Tuple[str, str]]然后才能确定它与我们对一副纸牌的表示相匹配。

Now consider how you would annotate deal_hands():

现在考虑如何注释deal_hands()

 def def deal_handsdeal_hands (
    (
    deckdeck : : ListList [[ TupleTuple [[ strstr , , strstr ]]
]]
) ) -> -> TupleTuple [
    [
    ListList [[ TupleTuple [[ strstr , , strstr ]],
    ]],
    ListList [[ TupleTuple [[ strstr , , strstr ]],
    ]],
    ListList [[ TupleTuple [[ strstr , , strstr ]],
    ]],
    ListList [[ TupleTuple [[ strstr , , strstr ]],
]],
]:
    ]:
    """Deal the cards in the deck into four hands"""
    """Deal the cards in the deck into four hands"""
    return return (( deckdeck [[ 00 :::: 44 ], ], deckdeck [[ 11 :::: 44 ], ], deckdeck [[ 22 :::: 44 ], ], deckdeck [[ 33 :::: 44 ])
])

That’s just terrible!

那太可怕了!

Recall that type annotations are regular Python expressions. That means that you can define your own type aliases by assigning them to new variables. You can for instance create Card and Deck type aliases:

回想一下,类型注释是常规的Python表达式。 这意味着您可以通过将它们分配给新变量来定义自己的类型别名。 例如,您可以创建CardDeck类型别名:

Card can now be used in type hints or in the definition of new type aliases, like Deck in the example above.

现在,可以在类型提示中或在新类型别名的定义中使用Card ,例如上例中的Deck

Using these aliases, the annotations of deal_hands() become much more readable:

使用这些别名, deal_hands()的注释变得更加可读:

 def def deal_handsdeal_hands (( deckdeck : : DeckDeck ) ) -> -> TupleTuple [[ DeckDeck , , DeckDeck , , DeckDeck , , DeckDeck ]:
    ]:
    """Deal the cards in the deck into four hands"""
    """Deal the cards in the deck into four hands"""
    return return (( deckdeck [[ 00 :::: 44 ], ], deckdeck [[ 11 :::: 44 ], ], deckdeck [[ 22 :::: 44 ], ], deckdeck [[ 33 :::: 44 ])
])

Type aliases are great for making your code and its intent clearer. At the same time, these aliases can be inspected to see what they represent:

类型别名非常适合使代码及其意图更清晰。 同时,可以检查这些别名以查看它们代表什么:

>>>
>>>
 >>>  from typing import List , Tuple
>>>  Card = Tuple [ str , str ]
>>>  Deck = List [ Card ]

>>>  Deck
typing.List[typing.Tuple[str, str]]

Note that when printing Deck, it shows that it’s an alias for a list of 2-tuples of strings.

请注意,在打印Deck ,它表明它是2元字符串列表的别名。

没有返回值的函数 (Functions Without Return Values)

You may know that functions without an explicit return still return None:

您可能知道没有显式返回的函数仍然返回None

>>>
>>>
 >>>  def play ( player_name ):
...     print ( f " {player_name}  plays" )
...

>>>  ret_val = play ( "Jacob" )
Jacob plays

>>>  print ( ret_val )
None

While such functions technically return something, that return value is not useful. You should add type hints saying as much by using None also as the return type:

尽管此类函数从技术上讲返回某些内容,但该返回值无用。 您还应该使用None作为返回类型来添加类型提示,以表示更多信息:

The annotations help catch the kinds of subtle bugs where you are trying to use a meaningless return value. Mypy will give you a helpful warning:

注释可帮助捕获试图使用无意义的返回值的各种细微错误。 Mypy将给您一个有用的警告:

 $ mypy play.py
$ mypy play.py
play.py:6: error: "play" does not return a value
play.py:6: error: "play" does not return a value

Note that being explicit about a function not returning anything is different from not adding a type hint about the return value:

请注意,明确声明函数不返回任何内容与不添加有关返回值的类型提示不同:

In this latter case Mypy has no information about the return value so it will not generate any warning:

在后一种情况下,Mypy没有有关返回值的信息,因此不会生成任何警告:

 $ mypy play.py
$ mypy play.py
$
$

As a more exotic case, note that you can also annotate functions that are never expected to return normally. This is done using NoReturn:

作为更特殊的情况,请注意,您还可以注释那些从未期望正常返回的函数。 这是使用NoReturn完成的:

Since black_hole() always raises an exception, it will never return properly.

由于black_hole()总是引发异常,因此它永远不会正确返回。

示例:打一些牌 (Example: Play Some Cards)

Let’s return to our card game example. In this second version of the game, we deal a hand of cards to each player as before. Then a start player is chosen and the players take turns playing their cards. There are not really any rules in the game though, so the players will just play random cards:

让我们返回纸牌游戏示例 。 在游戏的第二版中,我们像以前一样向每位玩家分发一副纸牌。 然后选择开始玩家,然后玩家轮流玩他们的纸牌。 游戏中实际上没有任何规则,因此玩家只会玩随机纸牌:

 # game.py

# game.py

import import random
random
from from typing typing import import ListList , , Tuple

Tuple

SUITS SUITS = = "♠ ♡ ♢ ♣""♠ ♡ ♢ ♣" .. splitsplit ()
()
RANKS RANKS = = "2 3 4 5 6 7 8 9 10 J Q K A""2 3 4 5 6 7 8 9 10 J Q K A" .. splitsplit ()

()

Card Card = = TupleTuple [[ strstr , , strstr ]
]
Deck Deck = = ListList [[ CardCard ]

]

def def create_deckcreate_deck (( shuffleshuffle : : bool bool = = FalseFalse ) ) -> -> DeckDeck :
    :
    """Create a new deck of 52 cards"""
    """Create a new deck of 52 cards"""
    deck deck = = [([( ss , , rr ) ) for for r r in in RANKS RANKS for for s s in in SUITSSUITS ]
    ]
    if if shuffleshuffle :
        :
        randomrandom .. shuffleshuffle (( deckdeck )
    )
    return return deck

deck

def def deal_handsdeal_hands (( deckdeck : : DeckDeck ) ) -> -> TupleTuple [[ DeckDeck , , DeckDeck , , DeckDeck , , DeckDeck ]:
    ]:
    """Deal the cards in the deck into four hands"""
    """Deal the cards in the deck into four hands"""
    return return (( deckdeck [[ 00 :::: 44 ], ], deckdeck [[ 11 :::: 44 ], ], deckdeck [[ 22 :::: 44 ], ], deckdeck [[ 33 :::: 44 ])

])

def def choosechoose (( itemsitems ):
    ):
    """Choose and return a random item"""
    """Choose and return a random item"""
    return return randomrandom .. choicechoice (( itemsitems )

)

def def player_orderplayer_order (( namesnames , , startstart == NoneNone ):
    ):
    """Rotate player order so that start goes first"""
    """Rotate player order so that start goes first"""
    if if start start is is NoneNone :
        :
        start start = = choosechoose (( namesnames )
    )
    start_idx start_idx = = namesnames .. indexindex (( startstart )
    )
    return return namesnames [[ start_idxstart_idx :] :] + + namesnames [:[: start_idxstart_idx ]

]

def def playplay () () -> -> NoneNone :
    :
    """Play a 4-player card game"""
    """Play a 4-player card game"""
    deck deck = = create_deckcreate_deck (( shuffleshuffle == TrueTrue )
    )
    names names = = "P1 P2 P3 P4""P1 P2 P3 P4" .. splitsplit ()
    ()
    hands hands = = {{ nn : : h h for for nn , , h h in in zipzip (( namesnames , , deal_handsdeal_hands (( deckdeck ))}
    ))}
    start_player start_player = = choosechoose (( namesnames )
    )
    turn_order turn_order = = player_orderplayer_order (( namesnames , , startstart == start_playerstart_player )

    )

    # Randomly play cards from each player's hand until empty
    # Randomly play cards from each player's hand until empty
    while while handshands [[ start_playerstart_player ]:
        ]:
        for for name name in in turn_orderturn_order :
            :
            card card = = choosechoose (( handshands [[ namename ])
            ])
            handshands [[ namename ]] .. removeremove (( cardcard )
            )
            printprint (( ff "" {name}{name} : {card[0] + card[1]:<3}  ": {card[0] + card[1]:<3}  " , , endend == """" )
        )
        printprint ()

()

if if __name__ __name__ == == "__main__""__main__" :
    :
    playplay ()
()

Note that in addition to changing play(), we have added two new functions that need type hints: choose() and player_order(). Before discussing how we’ll add type hints to them, here is an example output from running the game:

请注意,除了改变play()我们已经添加了两个新的功能,那需要类型提示: choose()player_order() 在讨论如何向它们添加类型提示之前,这是运行游戏的示例输出:

In this example, player P3 was randomly chosen as the starting player. In turn, each player plays a card: first P3, then P4, then P1, and finally P2. The players keep playing cards as long as they have any left in their hand.

在此示例中,玩家P3被随机选择为起始玩家。 依次地,每个玩家都玩牌:首先是P3 ,然后是P4 ,然后是P1 ,最后是P2 。 玩家只要手中有手牌,就会继续打牌。

Any类型 (The Any Type)

choose() works for both lists of names and lists of cards (and any other sequence for that matter). One way to add type hints for this would be the following:

choose()适用于名称列表和卡列表(以及与此相关的任何其他顺序)。 为此添加类型提示的一种方法是:

 import import random
random
from from typing typing import import AnyAny , , Sequence

Sequence

def def choosechoose (( itemsitems : : SequenceSequence [[ AnyAny ]) ]) -> -> AnyAny :
    :
    return return randomrandom .. choicechoice (( itemsitems )
)

This means more or less what it says: items is a sequence that can contain items of any type and choose() will return one such item of any type. Unfortunately, this is not that useful. Consider the following example:

这或多或少意味着它的含义: items是一个序列,可以包含任何类型的项目,而choose()将返回一个任何类型的此类项目。 不幸的是,这不是那么有用。 考虑以下示例:

While Mypy will correctly infer that names is a list of strings, that information is lost after the call to choose() because of the use of the Any type:

尽管Mypy可以正确推断names是字符串列表,但是由于使用Any类型,因此在调用select choose()之后,该信息会丢失:

 $ mypy choose.py
$ mypy choose.py
choose.py:10: error: Revealed type is 'builtins.list[builtins.str*]'
choose.py:10: error: Revealed type is 'builtins.list[builtins.str*]'
choose.py:13: error: Revealed type is 'Any'
choose.py:13: error: Revealed type is 'Any'

You’ll see a better way shortly. First though, let’s have a more theoretical look at the Python type system, and the special role Any plays.

您很快就会看到更好的方法。 首先,让我们从理论上更深入地了解Python类型系统,以及Any扮演的特殊角色。

类型理论 (Type Theory)

This tutorial is mainly a practical guide and we will only scratch the surface of the theory underpinning Python type hints. For more details PEP 483 is a good starting point. If you want to get back to the practical examples, feel free to skip to the next section.

本教程主要是一本实用指南,我们仅会介绍支持Python类型提示的理论知识。 有关更多详细信息, PEP 483是一个很好的起点。 如果您想返回到实际示例,请随时跳到下一部分

亚型 (Subtypes)

One important concept is that of subtypes. Formally, we say that a type T is a subtype of U if the following two conditions hold:

一个重要的概念是子类型 。 正式地说,如果满足以下两个条件,则类型TU的子类型:

  • Every value from T is also in the set of values of U type.
  • Every function from U type is also in the set of functions of T type.
  • 来自T每个值也属于U类型的值的集合。
  • U型的每个功能也都属于T型的功能。

These two conditions guarantees that even if type T is different from U, variables of type T can always pretend to be U.

这两个条件保证即使类型TU不同,类型T变量也总是可以假装为U

For a concrete example, consider T = bool and U = int. The bool type takes only two values. Usually these are denoted True and False, but these names are just aliases for the integer values 1 and 0, respectively:

举一个具体的例子,考虑T = boolU = intbool类型仅采用两个值。 通常,它们分别表示为TrueFalse ,但是这些名称分别只是整数值10别名:

>>>
>>>
 >>>  int ( False )
0

>>>  int ( True )
1

>>>  True + True
2

>>>  issubclass ( bool , int )
True

Since 0 and 1 are both integers, the first condition holds. Above you can see that booleans can be added together, but they can also do anything else integers can. This is the second condition above. In other words, bool is a subtype of int.

由于0和1都是整数,因此第一个条件成立。 在上面可以看到布尔值可以加在一起,但是它们也可以做整数可以做的其他事情。 这是上面的第二个条件。 换句话说, boolint的子类型。

The importance of subtypes is that a subtype can always pretend to be its supertype. For instance, the following code type checks as correct:

子类型的重要性在于,子类型总是可以假装为其父类型。 例如,以下代码类型检查是否正确:

Subtypes are somewhat related to subclasses. In fact all subclasses corresponds to subtypes, and bool is a subtype of int because bool is a subclass of int. However, there are also subtypes that do not correspond to subclasses. For instance int is a subtype of float, but int is not a subclass of float.

子类型与子类有些相关。 实际上,所有子类都对应于子类型,并且boolint的子类型,因为boolint的子类。 但是,也有一些子类型与子类不对应。 例如, intfloat的子类型,但int不是float的子类。

协变,逆变和不变 (Covariant, Contravariant, and Invariant)

What happens when you use subtypes inside composite types? For instance, is Tuple[bool] a subtype of Tuple[int]? The answer depends on the composite type, and whether that type is covariant, contravariant, or invariant. This gets technical fast, so let’s just give a few examples:

在复合类型中使用子类型时会发生什么? 例如, Tuple[bool]Tuple[int]的子类型吗? 答案取决于复合类型,以及该类型是协变,逆变还是不变 。 这可以快速获得技术,所以让我们举几个例子:

  • Tuple is covariant. This means that it preserves the type hierarchy of its item types: Tuple[bool] is a subtype of Tuple[int] because bool is a subtype of int.

  • List is invariant. Invariant types give no guarantee about subtypes. While all values of List[bool] are values of List[int], you can append an int to List[int] and not to List[bool]. In other words, the second condition for subtypes does not hold, and List[bool] is not a subtype of List[int].

  • Callable is contravariant in its arguments. This means that it reverses the type hierarchy. You will see how Callable works later, but for now think of Callable[[T], ...] as a function with its only argument being of type T. An example of a Callable[[int], ...] is the double() function defined above. Being contravariant means that if a function operating on a bool is expected, then a function operating on an int would be acceptable.

  • Tuple是协变的。 这意味着它将保留其项类型的类型层次结构: Tuple[bool]Tuple[int]的子类型,因为boolint的子类型。

  • List是不变的。 不变类型不能保证子类型。 虽然List[bool]所有值都是List[int]值,但是您可以将int附加到List[int]而不是List[bool] 。 换句话说,子类型的第二个条件不成立,并且List[bool]不是List[int]的子类型。

  • Callable在其参数上是相反的。 这意味着它将反转类型层次结构。 您将看到如何Callable的工作以后 ,但现在想Callable[[T], ...]作为其唯一的参数是类型的函数T 。 上面定义的double()函数是Callable[[int], ...]的示例。 变数意味着如果期望在bool函数上运行的函数,那么在int上运行的函数将是可以接受的。

In general, you don’t need to keep these expression straight. However, you should be aware that subtypes and composite types may not be simple and intuitive.

通常,您不需要保持这些表达式直截了当。 但是,您应该意识到,子类型和复合类型可能并不简单直观。

渐进式输入和一致类型 (Gradual Typing and Consistent Types)

Earlier we mentioned that Python supports gradual typing, where you can gradually add type hints to your Python code. Gradual typing is essentially made possible by the Any type.

之前我们提到Python支持渐进式输入 ,您可以在其中逐步将类型提示添加到Python代码中。 基本上,通过Any类型可以进行渐变键入。

Somehow Any sits both at the top and at the bottom of the type hierarchy of subtypes. Any type behaves as if it is a subtype of Any, and Any behaves as if it is a subtype of any other type. Looking at the definition of subtypes above this is not really possible. Instead we talk about consistent types.

无论如何, Any都位于子类型的类型层次结构的顶部和底部。 任何类型的行为就好像它是Any的子类型,而Any行为就好像它是任何其他类型的子类型。 从上面的子类型的定义来看,这实际上是不可能的。 相反,我们谈论一致类型

The type T is consistent with the type U if T is a subtype of U or either T or U is Any.

如果TU的子类型,或者TUAny ,则类型T与类型U一致。

The type checker only complains about inconsistent types. The takeaway is therefore that you will never see type errors arising from the Any type.

类型检查器仅抱怨类型不一致。 因此,总的来说,您将永远不会看到Any类型引起的类型错误。

This means that you can use Any to explicitly fall back to dynamic typing, describe types that are too complex to describe in the Python type system, or describe items in composite types. For instance, a dictionary with string keys that can take any type as its values can be annotated Dict[str, Any].

这意味着您可以使用Any显式地退回到动态类型,描述太复杂而无法在Python类型系统中描述的类型,或描述复合类型的项目。 例如,带有字符串键的字典可以采用任何类型,因为其值可以标注Dict[str, Any]

Do remember, though, if you use Any the static type checker will effectively not do any type any checking.

不过请记住,如果您使用Any则静态类型检查器实际上将不会执行任何类型的任何检查。

玩Python类型,第2部分 (Playing With Python Types, Part 2)

Let’s return to our practical examples. Recall that you were trying to annotate the general choose() function:

让我们回到我们的实际例子。 回想一下您试图注释一般的choose()函数:

 import import random
random
from from typing typing import import AnyAny , , Sequence

Sequence

def def choosechoose (( itemsitems : : SequenceSequence [[ AnyAny ]) ]) -> -> AnyAny :
    :
    return return randomrandom .. choicechoice (( itemsitems )
)

The problem with using Any is that you are needlessly losing type information. You know that if you pass a list of strings to choose(), it will return a string. Below you’ll see how to express this using type variables, as well as how to work with:

使用Any的问题在于,您不必要地丢失了类型信息。 您知道,如果将字符串列表传递给choose() ,它将返回一个字符串。 在下面,您将看到如何使用类型变量来表达这一点,以及如何使用它:

类型变量 (Type Variables)

A type variable is a special variable that can take on any type, depending on the situation.

类型变量是一种特殊的变量,可以根据情况采用任何类型。

Let’s create a type variable that will effectively encapsulate the behavior of choose():

让我们创建一个类型变量,该变量将有效地封装choose()的行为:

A type variable must be defined using TypeVar from the typing module. When used, a type variable ranges over all possible types and takes the most specific type possible. In the example, name is now a str:

必须使用typing模块中的TypeVar定义类型变量。 当使用类型变量时,其类型范围涵盖所有可能的类型,并采用最具体的类型。 在示例中, name现在是str

 $ mypy choose.py
$ mypy choose.py
choose.py:12: error: Revealed type is 'builtins.list[builtins.str*]'
choose.py:12: error: Revealed type is 'builtins.list[builtins.str*]'
choose.py:15: error: Revealed type is 'builtins.str*'
choose.py:15: error: Revealed type is 'builtins.str*'

Consider a few other examples:

考虑其他一些示例:

The first two examples should have type str and int, but what about the last two? The individual list items have different types, and in that case the Choosable type variable does its best to accommodate:

前两个示例的类型应该为strint ,但是后两个示例呢? 各个列表项具有不同的类型,在这种情况下, Choosable type变量会尽最大努力适应以下情况:

 $ mypy choose_examples.py
$ mypy choose_examples.py
choose_examples.py:5: error: Revealed type is 'builtins.str*'
choose_examples.py:5: error: Revealed type is 'builtins.str*'
choose_examples.py:6: error: Revealed type is 'builtins.int*'
choose_examples.py:6: error: Revealed type is 'builtins.int*'
choose_examples.py:7: error: Revealed type is 'builtins.float*'
choose_examples.py:7: error: Revealed type is 'builtins.float*'
choose_examples.py:8: error: Revealed type is 'builtins.object*'
choose_examples.py:8: error: Revealed type is 'builtins.object*'

As you’ve already seen bool is a subtype of int, which again is a subtype of float. So in the third example the return value of choose() is guaranteed to be something that can be thought of as a float. In the last example, there is no subtype relationship between str and int, so the best that can be said about the return value is that it is an object.

您已经看到boolint的子类型,再次是float的子类型。 因此,在第三个示例中,可以保证choose()的返回值可以看作是float 。 在最后一个示例中, strint之间没有子类型关系,因此关于返回值的最好说就是它是一个对象。

Note that none of these examples raised a type error. Is there a way to tell the type checker that choose() should accept both strings and numbers, but not both at the same time?

请注意,这些示例均未引发类型错误。 有没有办法告诉类型检查器select choose()应该接受字符串和数字,但不能同时接受?

You can constrain type variables by listing the acceptable types:

您可以通过列出可接受的类型来约束类型变量:

Now Choosable can only be either str or float, and Mypy will note that the last example is an error:

现在Choosable只能是strfloat ,而Mypy将注意到最后一个示例是一个错误:

 $ mypy choose.py
$ mypy choose.py
choose.py:11: error: Revealed type is 'builtins.str*'
choose.py:11: error: Revealed type is 'builtins.str*'
choose.py:12: error: Revealed type is 'builtins.float*'
choose.py:12: error: Revealed type is 'builtins.float*'
choose.py:13: error: Revealed type is 'builtins.float*'
choose.py:13: error: Revealed type is 'builtins.float*'
choose.py:14: error: Revealed type is 'builtins.object*'
choose.py:14: error: Revealed type is 'builtins.object*'
choose.py:14: error: Value of type variable "Choosable" of "choose"
choose.py:14: error: Value of type variable "Choosable" of "choose"
                     cannot be "object"
                     cannot be "object"

Also note that in the second example the type is considered float even though the input list only contains int objects. This is because Choosable was restricted to strings and floats and int is a subtype of float.

还要注意,在第二个示例中,即使输入列表仅包含int对象,该类型也被认为是float类型。 这是因为Choosable仅限于字符串和float,而intfloat的子类型。

In our card game we want to restrict choose() to be used for str and Card:

在我们的纸牌游戏中,我们希望限制将choose()用于strCard

We briefly mentioned that Sequence represents both lists and tuples. As we noted, a Sequence can be thought of as a duck type, since it can be any object with .__len__() and .__getitem__() implemented.

我们简要提到了Sequence既代表列表又代表元组。 如前所述, Sequence可以看作是鸭子类型,因为它可以是实现了.__len__().__getitem__()任何对象。

鸭子的类型和协议 (Duck Types and Protocols)

Recall the following example from the introduction:

回顾一下引言中的以下示例:

 def def lenlen (( objobj ):
    ):
    return return objobj .. __len____len__ ()
()

len() can return the length of any object that has implemented the .__len__() method. How can we add type hints to len(), and in particular the obj argument?

len()可以返回已实现.__len__()方法的任何对象的长度。 我们如何向len()尤其是obj参数添加类型提示?

The answer hides behind the academic sounding term structural subtyping. One way to categorize type systems is by whether they are nominal or structural:

答案隐藏在学术用语结构亚型的背后。 对类型系统进行分类的一种方法是根据它们是名义上的还是结构上的

  • In a nominal system, comparisons between types are based on names and declarations. The Python type system is mostly nominal, where an int can be used in place of a float because of their subtype relationship.

  • In a structural system, comparisons between types are based on structure. You could define a structural type Sized that includes all instances that define .__len__(), irrespective of their nominal type.

  • 名义系统中,类型之间的比较基于名称和声明。 Python类型系统通常是名义上的,由于其子类型关系,可以使用int代替float

  • 结构系统中,类型之间的比较是基于结构的。 您可以定义结构类型Sized ,包括所有定义.__len__()实例,无论其名义类型如何。

There is ongoing work to bring a full-fledged structural type system to Python through PEP 544 which aims at adding a concept called protocols. Most of PEP 544 is already implemented in Mypy though.

目前正在进行通过PEP 544将成熟的结构类型系统引入Python的工作,该系统旨在添加称为协议的概念。 不过,大多数PEP 544已在Mypy中实现

A protocol specifies one or more methods that must be implemented. For example, all classes defining .__len__() fulfill the typing.Sized protocol. We can therefore annotate len() as follows:

协议指定必须实施的一种或多种方法。 例如,所有定义.__len__()满足typing.Sized协议。 因此,我们可以如下注释len()

Other examples of protocols defined in the typing module include Container, Iterable, Awaitable, and ContextManager.

typing模块中定义的协议的其他示例包括ContainerIterableAwaitableContextManager

You can also define your own protocols. This is done by inheriting from Protocol and defining the function signatures (with empty function bodies) that the protocol expects. The following example shows how len() and Sized could have been implemented:

您还可以定义自己的协议。 这是通过从Protocol继承并定义协议期望的功能签名(带有空功能体)来完成的。 以下示例显示了如何实现len()Sized

 from from typing_extensions typing_extensions import import Protocol

Protocol

class class SizedSized (( ProtocolProtocol ):
    ):
    def def __len____len__ (( selfself ) ) -> -> intint : : ...

...

def def lenlen (( objobj : : SizedSized ) ) -> -> intint :
    :
    return return objobj .. __len____len__ ()
()

At the time of writing the support for self-defined protocols is still experimental and only available through the typing_extensions module. This module must be explicitly installed from PyPI by doing pip install typing-extensions.

在撰写本文时,对自定义协议的支持仍处于试验阶段,仅可通过typing_extensions模块获得。 必须通过执行pip install typing-extensionsPyPI显式安装此模块。

Optional类型 (The Optional Type)

A common pattern in Python is to use None as a default value for an argument. This is usually done either to avoid problems with mutable default values or to have a sentinel value flagging special behavior.

Python中的常见模式是将None用作参数的默认值。 通常这样做是为了避免可变默认值出现问题,或者使标记值标记特殊行为。

In the card example, the player_order() function uses None as a sentinel value for start saying that if no start player is given it should be chosen randomly:

在纸牌示例中, player_order()函数使用None作为start的哨兵值,即如果未给出开始玩家,则应随机选择:

The challenge this creates for type hinting is that in general start should be a string. However, it may also take the special non-string value None.

这给类型提示带来的挑战是,通常start应该是一个字符串。 但是,它也可以采用特殊的非字符串值None

In order to annotate such arguments you can use the Optional type:

为了注释此类参数,您可以使用Optional类型:

 from from typing typing import import SequenceSequence , , Optional

Optional

def def player_orderplayer_order (
    (
    namesnames : : SequenceSequence [[ strstr ], ], startstart : : OptionalOptional [[ strstr ] ] = = None
None
) ) -> -> SequenceSequence [[ strstr ]:
    ]:
    ...
...

The Optional type simply says that a variable either has the type specified or is None. An equivalent way of specifying the same would be using the Union type: Union[None, str]

Optional类型仅表示变量具有指定的类型或为None 。 一种等效的指定方法是使用Union类型: Union[None, str]

Note that when using either Optional or Union you must take care that the variable has the correct type when you operate on it. This is done in the example by testing whether start is None. Not doing so would cause both static type errors as well as possible runtime errors:

请注意,在使用OptionalUnion ,必须注意变量在操作时具有正确的类型。 在示例中,这是通过测试start is None是否start is None 。 否则会导致静态类型错误以及可能的运行时错误:

Mypy tells you that you have not taken care of the case where start is None:

Mypy告诉您您没有注意startNone的情况:

 $ mypy player_order.py
$ mypy player_order.py
player_order.py:8: error: Argument 1 to "index" of "list" has incompatible
player_order.py:8: error: Argument 1 to "index" of "list" has incompatible
                          type "Optional[str]"; expected "str"
                          type "Optional[str]"; expected "str"

Note: The use of None for optional arguments is so common that Mypy handles it automatically. Mypy assumes that a default argument of None indicates an optional argument even if the type hint does not explicitly say so. You could have used the following:

注意:对可选参数使用None非常普遍,Mypy会自动对其进行处理。 Mypy假定即使类型提示没有明确指出,默认参数None表示可选参数。 您可能已经使用了以下内容:

If you don’t want Mypy to make this assumption you can turn it off with the --no-implicit-optional command line option.

如果您不希望Mypy做出此假设,则可以使用--no-implicit-optional命令行选项将其关闭。

示例:游戏的目标 (Example: The Object(ive) of the Game)

Let’s rewrite the card game to be more object-oriented. This will allow us to discuss how to properly annotate classes and methods.

让我们将纸牌游戏改写为更加面向对象 。 这将使我们讨论如何正确注释类和方法。

A more or less direct translation of our card game into code that uses classes for Card, Deck, Player, and Game looks something like the following:

将纸牌游戏或多或少直接转换为使用CardDeckPlayerGame类的代码,如下所示:

 # game.py

# game.py

import import random
random
import import sys

sys

class class CardCard :
    :
    SUITS SUITS = = "♠ ♡ ♢ ♣""♠ ♡ ♢ ♣" .. splitsplit ()
    ()
    RANKS RANKS = = "2 3 4 5 6 7 8 9 10 J Q K A""2 3 4 5 6 7 8 9 10 J Q K A" .. splitsplit ()

    ()

    def def __init____init__ (( selfself , , suitsuit , , rankrank ):
        ):
        selfself .. suit suit = = suit
        suit
        selfself .. rank rank = = rank

    rank

    def def __repr____repr__ (( selfself ):
        ):
        return return ff "" {self.suit}{self.rank}{self.suit}{self.rank} "

"

class class DeckDeck :
    :
    def def __init____init__ (( selfself , , cardscards ):
        ):
        selfself .. cards cards = = cards

    cards

    @classmethod
    @classmethod
    def def createcreate (( clscls , , shuffleshuffle == FalseFalse ):
        ):
        """Create a new deck of 52 cards"""
        """Create a new deck of 52 cards"""
        cards cards = = [[ CardCard (( ss , , rr ) ) for for r r in in CardCard .. RANKS RANKS for for s s in in CardCard .. SUITSSUITS ]
        ]
        if if shuffleshuffle :
            :
            randomrandom .. shuffleshuffle (( cardscards )
        )
        return return clscls (( cardscards )

    )

    def def dealdeal (( selfself , , num_handsnum_hands ):
        ):
        """Deal the cards in the deck into a number of hands"""
        """Deal the cards in the deck into a number of hands"""
        cls cls = = selfself .. __class__
        __class__
        return return tupletuple (( clscls (( selfself .. cardscards [[ ii :::: num_handsnum_hands ]) ]) for for i i in in rangerange (( num_handsnum_hands ))

))

class class PlayerPlayer :
    :
    def def __init____init__ (( selfself , , namename , , handhand ):
        ):
        selfself .. name name = = name
        name
        selfself .. hand hand = = hand

    hand

    def def play_cardplay_card (( selfself ):
        ):
        """Play a card from the player's hand"""
        """Play a card from the player's hand"""
        card card = = randomrandom .. choicechoice (( selfself .. handhand .. cardscards )
        )
        selfself .. handhand .. cardscards .. removeremove (( cardcard )
        )
        printprint (( ff "" {self.name}{self.name} : :  {card!r:<3}{card!r:<3}   "  " , , endend == """" )
        )
        return return card

card

class class GameGame :
    :
    def def __init____init__ (( selfself , , ** namesnames ):
        ):
        """Set up the deck and deal cards to 4 players"""
        """Set up the deck and deal cards to 4 players"""
        deck deck = = DeckDeck .. createcreate (( shuffleshuffle == TrueTrue )
        )
        selfself .. names names = = (( listlist (( namesnames ) ) + + "P1 P2 P3 P4""P1 P2 P3 P4" .. splitsplit ())[:())[: 44 ]
        ]
        selfself .. hands hands = = {
            {
            nn : : PlayerPlayer (( nn , , hh ) ) for for nn , , h h in in zipzip (( selfself .. namesnames , , deckdeck .. dealdeal (( 44 ))
        ))
        }

    }

    def def playplay (( selfself ):
        ):
        """Play a card game"""
        """Play a card game"""
        start_player start_player = = randomrandom .. choicechoice (( selfself .. namesnames )
        )
        turn_order turn_order = = selfself .. player_orderplayer_order (( startstart == start_playerstart_player )

        )

        # Play cards from each player's hand until empty
        # Play cards from each player's hand until empty
        while while selfself .. handshands [[ start_playerstart_player ]] .. handhand .. cardscards :
            :
            for for name name in in turn_orderturn_order :
                :
                selfself .. handshands [[ namename ]] .. play_cardplay_card ()
            ()
            printprint ()

    ()

    def def player_orderplayer_order (( selfself , , startstart == NoneNone ):
        ):
        """Rotate player order so that start goes first"""
        """Rotate player order so that start goes first"""
        if if start start is is NoneNone :
            :
            start start = = randomrandom .. choicechoice (( selfself .. namesnames )
        )
        start_idx start_idx = = selfself .. namesnames .. indexindex (( startstart )
        )
        return return selfself .. namesnames [[ start_idxstart_idx :] :] + + selfself .. namesnames [:[: start_idxstart_idx ]

]

if if __name__ __name__ == == "__main__""__main__" :
    :
    # Read player names from command line
    # Read player names from command line
    player_names player_names = = syssys .. argvargv [[ 11 :]
    :]
    game game = = GameGame (( ** player_namesplayer_names )
    )
    gamegame .. playplay ()
()

Now let’s add types to this code.

现在让我们向该代码添加类型。

方法的类型提示 (Type Hints for Methods)

First of all type hints for methods work much the same as type hints for functions. The only difference is that the self argument need not be annotated, as it always will be a class instance. The types of the Card class are easy to add:

首先,方法的类型提示与函数的类型提示工作原理大致相同。 唯一的区别是self参数不需要注释,因为它总是一个类实例。 Card类的类型很容易添加:

Note that the .__init__() method always should have None as its return type.

请注意, .__init__()方法始终应将None作为其返回类型。

类作为类型 (Classes as Types)

There is a correspondence between classes and types. For example, all instances of the Card class together form the Card type. To use classes as types you simply use the name of the class.

类和类型之间存在对应关系。 例如, Card类的所有实例一起构成Card类型。 要将类用作类型,您只需使用类的名称。

For example, a Deck essentially consists of a list of Card objects. You can annotate this as follows:

例如,一个Deck本质上由Card对象列表组成。 您可以如下注释:

 class class DeckDeck :
    :
    def def __init____init__ (( selfself , , cardscards : : ListList [[ CardCard ]) ]) -> -> NoneNone :
        :
        selfself .. cards cards = = cards
cards

Mypy is able to connect your use of Card in the annotation with the definition of the Card class.

Mypy可以将您在Card中的使用与Card类的定义联系起来。

This doesn’t work as cleanly though when you need to refer to the class currently being defined. For example, the Deck.create() class method returns an object with type Deck. However, you can’t simply add -> Deck as the Deck class is not yet fully defined.

但是,当您需要引用当前正在定义的类时,这种方法就无法正常工作了。 例如, Deck.create()类方法返回一个类型为Deck的对象。 但是,您不能简单地添加-> Deck因为Deck类尚未完全定义。

Instead, you are allowed to use string literals in annotations. These strings will only be evaluated by the type checker later, and can therefore contain self and forward references. The .create() method should use such string literals for its types:

相反,允许您在注释中使用字符串文字。 这些字符串仅在以后由类型检查器评估,因此可以包含自身和正向引用。 .create()方法的类型应使用以下字符串文字:

Note that the Player class also will reference the Deck class. This is however no problem, since Deck is defined before Player:

请注意, Player类也将引用Deck类。 但这没问题,因为Deck是在Player之前定义的:

 class class PlayerPlayer :
    :
    def def __init____init__ (( selfself , , namename : : strstr , , handhand : : DeckDeck ) ) -> -> NoneNone :
        :
        selfself .. name name = = name
        name
        selfself .. hand hand = = hand
hand

Usually annotations are not used at runtime. This has given wings to the idea of postponing the evaluation of annotations. Instead of evaluating annotations as Python expressions and storing their value, the proposal is to store the string representation of the annotation and only evaluate it when needed.

通常在运行时不使用注释。 这为推迟注释的评估提供了思路。 建议不是存储注释作为Python表达式并存储其值,而是建议存储注释的字符串表示形式,并仅在需要时对其进行计算。

Such functionality is planned to become standard in the still mythical Python 4.0. However, in Python 3.7 and later, forward references are available through a __future__ import:

计划在仍然神话般的Python 4.0中使这种功能成为标准。 但是,在Python 3.7和更高版本中,可以通过__future__导入获得前向引用:

With the __future__ import you can use Deck instead of "Deck" even before Deck is defined.

随着__future__进口可以用Deck ,而不是"Deck" ,甚至前Deck被定义。

回到selfcls (Returning self or cls)

As noted, you should typically not annotate the self or cls arguments. Partly, this is not necessary as self points to an instance of the class, so it will have the type of the class. In the Card example, self has the implicit type Card. Also, adding this type explicitly would be cumbersome since the class is not defined yet. You would have to use the string literal syntax, self: "Card".

如前所述,通常不应注释selfcls参数。 在某种程度上,由于self指向类的实例是不必要的,因此它将具有类的类型。 在Card示例中, self具有隐式类型Card 。 此外,由于尚未定义该类,因此显式添加此类型将很麻烦。 您将必须使用字符串文字语法self: "Card"

There is one case where you might want to annotate self or cls, though. Consider what happens if you have a superclass that other classes inherit from, and which has methods that return self or cls:

cls情况下,您可能想注释selfcls 。 考虑一下,如果您有一个其他类继承的超类,并且cls具有返回selfcls方法,将会发生什么?

 # dogs.py

# dogs.py

from from datetime datetime import import date

date

class class AnimalAnimal :
    :
    def def __init____init__ (( selfself , , namename : : strstr , , birthdaybirthday : : datedate ) ) -> -> NoneNone :
        :
        selfself .. name name = = name
        name
        selfself .. birthday birthday = = birthday

    birthday

    @classmethod
    @classmethod
    def def newbornnewborn (( clscls , , namename : : strstr ) ) -> -> "Animal""Animal" :
        :
        return return clscls (( namename , , datedate .. todaytoday ())

    ())

    def def twintwin (( selfself , , namename : : strstr ) ) -> -> "Animal""Animal" :
        :
        cls cls = = selfself .. __class__
        __class__
        return return clscls (( namename , , selfself .. birthdaybirthday )

)

class class DogDog (( AnimalAnimal ):
    ):
    def def barkbark (( selfself ) ) -> -> NoneNone :
        :
        printprint (( ff "" {self.name}{self.name}  says woof!" says woof!" )

)

fido fido = = DogDog .. newbornnewborn (( "Fido""Fido" )
)
pluto pluto = = fidofido .. twintwin (( "Pluto""Pluto" )
)
fidofido .. barkbark ()
()
plutopluto .. barkbark ()
()

While the code runs without problems, Mypy will flag a problem:

当代码运行没有问题时,Mypy将标记问题:

The issue is that even though the inherited Dog.newborn() and Dog.twin() methods will return a Dog the annotation says that they return an Animal.

问题是,即使继承的Dog.newborn()Dog.twin()方法将返回Dog ,注释也指出它们返回了Animal

In cases like this you want to be more careful to make sure the annotation is correct. The return type should match the type of self or the instance type of cls. This can be done using type variables that keep track of what is actually passed to self and cls:

在这种情况下,您需要更加小心以确保注释正确。 返回类型应匹配self的类型或cls的实例类型。 这可以通过使用类型变量来完成,该变量跟踪实际传递给selfcls

 # dogs.py

# dogs.py

from from datetime datetime import import date
date
from from typing typing import import TypeType , , TypeVar

TypeVar

TAnimal TAnimal = = TypeVarTypeVar (( "TAnimal""TAnimal" , , boundbound == "Animal""Animal" )

)

class class AnimalAnimal :
    :
    def def __init____init__ (( selfself , , namename : : strstr , , birthdaybirthday : : datedate ) ) -> -> NoneNone :
        :
        selfself .. name name = = name
        name
        selfself .. birthday birthday = = birthday

    birthday

    @classmethod
    @classmethod
    def def newbornnewborn (( clscls : : TypeType [[ TAnimalTAnimal ], ], namename : : strstr ) ) -> -> TAnimalTAnimal :
        :
        return return clscls (( namename , , datedate .. todaytoday ())

    ())

    def def twintwin (( selfself : : TAnimalTAnimal , , namename : : strstr ) ) -> -> TAnimalTAnimal :
        :
        cls cls = = selfself .. __class__
        __class__
        return return clscls (( namename , , selfself .. birthdaybirthday )

)

class class DogDog (( AnimalAnimal ):
    ):
    def def barkbark (( selfself ) ) -> -> NoneNone :
        :
        printprint (( ff "" {self.name}{self.name}  says woof!" says woof!" )

)

fido fido = = DogDog .. newbornnewborn (( "Fido""Fido" )
)
pluto pluto = = fidofido .. twintwin (( "Pluto""Pluto" )
)
fidofido .. barkbark ()
()
plutopluto .. barkbark ()
()

There are a few things to note in this example:

此示例中有几件事要注意:

  • The type variable TAnimal is used to denote that return values might be instances of subclasses of Animal.

  • We specify that Animal is an upper bound for TAnimal. Specifying bound means that TAnimal will only be Animal or one of its subclasses. This is needed to properly restrict the types that are allowed.

  • The typing.Type[] construct is the typing equivalent of type(). You need it to note that the class method expects a class and returns an instance of that class.

  • 类型变量TAnimal用于表示返回值可能是Animal的子类的实例。

  • 我们指定AnimalTAnimal的上限。 指定bound意味着TAnimal将仅是Animal或其子类之一。 这是正确限制允许的类型所必需的。

  • typing.Type[]构造与type()的键入等效。 您需要注意类方法需要一个类并返回该类的实例。

注释*args**kwargs (Annotating *args and **kwargs)

In the object oriented version of the game, we added the option to name the players on the command line. This is done by listing player names after the name of the program:

面向对象的游戏版本中,我们添加了在命令行上命名玩家的选项。 通过在程序名称后列出播放器名称来完成此操作:

This is implemented by unpacking and passing in sys.argv to Game() when it’s instantiated. The .__init__() method uses *names to pack the given names into a tuple.

这是通过实例化sys.argv并将其传递给Game()来实现的。 .__init__()方法使用*names将给定名称打包为元组。

Regarding type annotations: even though names will be a tuple of strings, you should only annotate the type of each name. In other words, you should use str and not Tuple[str]:

关于类型注释:即使names将是字符串的元组,您也应仅注释每个名称的类型。 换句话说,您应该使用str而不是Tuple[str]

 class class GameGame :
    :
    def def __init____init__ (( selfself , , ** namesnames : : strstr ) ) -> -> NoneNone :
        :
        """Set up the deck and deal cards to 4 players"""
        """Set up the deck and deal cards to 4 players"""
        deck deck = = DeckDeck .. createcreate (( shuffleshuffle == TrueTrue )
        )
        selfself .. names names = = (( listlist (( namesnames ) ) + + "P1 P2 P3 P4""P1 P2 P3 P4" .. splitsplit ())[:())[: 44 ]
        ]
        selfself .. hands hands = = {
            {
            nn : : PlayerPlayer (( nn , , hh ) ) for for nn , , h h in in zipzip (( selfself .. namesnames , , deckdeck .. dealdeal (( 44 ))
        ))
        }
}

Similarly, if you have a function or method accepting **kwargs you should only annotate the type of each possible keyword argument.

同样,如果您有接受**kwargs的函数或方法,则应仅注释每个可能的关键字参数的类型。

可赎回 (Callables)

Functions are first-class objects in Python. This means that you can use functions as arguments to other functions. That also means that you need to be able to add type hints representing functions.

函数是Python中的一流对象 。 这意味着您可以将函数用作其他函数的参数。 这也意味着您需要能够添加表示函数的类型提示。

Functions, as well as lambdas, methods and classes, are represented by typing.Callable. The types of the arguments and the return value are usually also represented. For instance, Callable[[A1, A2, A3], Rt] represents a function with three arguments with types A1, A2, and A3, respectively. The return type of the function is Rt.

函数以及lambda,方法和类均由typing.Callable表示 。 通常还表示参数的类型和返回值。 例如, Callable[[A1, A2, A3], Rt]表示一个函数,具有三个分别为A1A2A3类型的参数。 该函数的返回类型为Rt

In the following example, the function do_twice() calls a given function twice and prints the return values:

在下面的示例中,函数do_twice()调用给定函数两次并输出返回值:

Note the annotation of the func argument to do_twice() on line 5. It says that func should be a callable with one string argument, that also returns a string. One example of such a callable is create_greeting() defined on line 9.

请注意第5行上do_twice()func参数的注释。它表示func应该是一个带有一个字符串参数的可调用对象,它还返回一个字符串。 第9行中定义的create_greeting()就是这样的可调用示例。

Most callable types can be annotated in a similar manner. However, if you need more flexibility, check out callback protocols and extended callable types.

大多数可调用类型都可以用类似的方式注释。 但是,如果您需要更大的灵活性,请查看回调协议扩展的可调用类型

示例:心 (Example: Hearts)

Let’s end with a full example of the game of Hearts. You might already know this game from other computer simulations. Here is a quick recap of the rules:

让我们以“ ”游戏的完整示例结尾。 您可能已经从其他计算机模拟中了解了这款游戏。 以下是规则的简要介绍:

  • Four players play with a hand of 13 cards each.

  • The player holding the ♣2 starts the first round, and must play ♣2.

  • Players take turns playing cards, following the leading suit if possible.

  • The player playing the highest card in the leading suit wins the trick, and becomes start player in the next turn.

  • A player can not lead with a ♡ until a ♡ has already been played in an earlier trick.

  • After all cards are played, players get points if they take certain cards:

    • 13 points for the ♠Q
    • 1 point for each ♡
  • A game lasts several rounds, until one player has 100 points or more. The player with the least points wins.

  • 四名玩家各有13张牌。

  • 持有♣2的玩家在第一轮开始,必须下注♣2。

  • 玩家轮流玩纸牌,如果可能的话跟随领队。

  • 领先套装中玩最高牌的玩家将赢得花样,并在下一轮中成为开始玩家。

  • 除非在较早的技巧中玩过♡,否则玩家无法领先♡。

  • 玩完所有纸牌后,如果玩家持有某些纸牌,则可以得到积分:

    • pointsQ 13分
    • 每个♡1分
  • 一场游戏持续数轮,直到一位玩家获得100分以上。 得分最少的玩家获胜。

More details can be found found online.

可以在网上找到更多详细信息。

There are not many new typing concepts in this example that you have not already seen. We’ll therefore not go through this code in detail, but leave it as an example of annotated code.

在此示例中,您还没有看到很多新的键入概念。 因此,我们将不详细介绍此代码,而将其作为带注释的代码的示例。

You can download this code and other examples from GitHub:

您可以从GitHub下载此代码和其他示例:

 # hearts.py

# hearts.py

from from collections collections import import Counter
Counter
import import random
random
import import sys
sys
from from typing typing import import AnyAny , , DictDict , , ListList , , OptionalOptional , , SequenceSequence , , TupleTuple , , Union
Union
from from typing typing import import overload

overload

class class CardCard :
    :
    SUITS SUITS = = "♠ ♡ ♢ ♣""♠ ♡ ♢ ♣" .. splitsplit ()
    ()
    RANKS RANKS = = "2 3 4 5 6 7 8 9 10 J Q K A""2 3 4 5 6 7 8 9 10 J Q K A" .. splitsplit ()

    ()

    def def __init____init__ (( selfself , , suitsuit : : strstr , , rankrank : : strstr ) ) -> -> NoneNone :
        :
        selfself .. suit suit = = suit
        suit
        selfself .. rank rank = = rank

    rank

    @property
    @property
    def def valuevalue (( selfself ) ) -> -> intint :
        :
        """The value of a card is rank as a number"""
        """The value of a card is rank as a number"""
        return return selfself .. RANKSRANKS .. indexindex (( selfself .. rankrank )

    )

    @property
    @property
    def def pointspoints (( selfself ) ) -> -> intint :
        :
        """Points this card is worth"""
        """Points this card is worth"""
        if if selfself .. suit suit == == "♠" "♠" and and selfself .. rank rank == == "Q""Q" :
            :
            return return 13
        13
        if if selfself .. suit suit == == "♡""♡" :
            :
            return return 1
        1
        return return 0

    0

    def def __eq____eq__ (( selfself , , otherother : : AnyAny ) ) -> -> AnyAny :
        :
        return return selfself .. suit suit == == otherother .. suit suit and and selfself .. rank rank == == otherother .. rank

    rank

    def def __lt____lt__ (( selfself , , otherother : : AnyAny ) ) -> -> AnyAny :
        :
        return return selfself .. value value < < otherother .. value

    value

    def def __repr____repr__ (( selfself ) ) -> -> strstr :
        :
        return return ff "" {self.suit}{self.rank}{self.suit}{self.rank} "

"

class class DeckDeck (( SequenceSequence [[ CardCard ]):
    ]):
    def def __init____init__ (( selfself , , cardscards : : ListList [[ CardCard ]) ]) -> -> NoneNone :
        :
        selfself .. cards cards = = cards

    cards

    @classmethod
    @classmethod
    def def createcreate (( clscls , , shuffleshuffle : : bool bool = = FalseFalse ) ) -> -> "Deck""Deck" :
        :
        """Create a new deck of 52 cards"""
        """Create a new deck of 52 cards"""
        cards cards = = [[ CardCard (( ss , , rr ) ) for for r r in in CardCard .. RANKS RANKS for for s s in in CardCard .. SUITSSUITS ]
        ]
        if if shuffleshuffle :
            :
            randomrandom .. shuffleshuffle (( cardscards )
        )
        return return clscls (( cardscards )

    )

    def def playplay (( selfself , , cardcard : : CardCard ) ) -> -> NoneNone :
        :
        """Play one card by removing it from the deck"""
        """Play one card by removing it from the deck"""
        selfself .. cardscards .. removeremove (( cardcard )

    )

    def def dealdeal (( selfself , , num_handsnum_hands : : intint ) ) -> -> TupleTuple [[ "Deck""Deck" , , ...... ]:
        ]:
        """Deal the cards in the deck into a number of hands"""
        """Deal the cards in the deck into a number of hands"""
        return return tupletuple (( selfself [[ ii :::: num_handsnum_hands ] ] for for i i in in rangerange (( num_handsnum_hands ))

    ))

    def def add_cardsadd_cards (( selfself , , cardscards : : ListList [[ CardCard ]) ]) -> -> NoneNone :
        :
        """Add a list of cards to the deck"""
        """Add a list of cards to the deck"""
        selfself .. cards cards += += cards

    cards

    def def __len____len__ (( selfself ) ) -> -> intint :
        :
        return return lenlen (( selfself .. cardscards )

    )

    @overload
    @overload
    def def __getitem____getitem__ (( selfself , , keykey : : intint ) ) -> -> CardCard : : ...

    ...

    @overload
    @overload
    def def __getitem____getitem__ (( selfself , , keykey : : sliceslice ) ) -> -> "Deck""Deck" : : ...

    ...

    def def __getitem____getitem__ (( selfself , , keykey : : UnionUnion [[ intint , , sliceslice ]) ]) -> -> UnionUnion [[ CardCard , , "Deck""Deck" ]:
        ]:
        if if isinstanceisinstance (( keykey , , intint ):
            ):
            return return selfself .. cardscards [[ keykey ]
        ]
        elif elif isinstanceisinstance (( keykey , , sliceslice ):
            ):
            cls cls = = selfself .. __class__
            __class__
            return return clscls (( selfself .. cardscards [[ keykey ])
        ])
        elseelse :
            :
            raise raise TypeErrorTypeError (( "Indices must be integers or slices""Indices must be integers or slices" )

    )

    def def __repr____repr__ (( selfself ) ) -> -> strstr :
        :
        return return " "" " .. joinjoin (( reprrepr (( cc ) ) for for c c in in selfself .. cardscards )

)

class class PlayerPlayer :
    :
    def def __init____init__ (( selfself , , namename : : strstr , , handhand : : OptionalOptional [[ DeckDeck ] ] = = NoneNone ) ) -> -> NoneNone :
        :
        selfself .. name name = = name
        name
        selfself .. hand hand = = DeckDeck ([]) ([]) if if hand hand is is None None else else hand

    hand

    def def playable_cardsplayable_cards (( selfself , , playedplayed : : ListList [[ CardCard ], ], hearts_brokenhearts_broken : : boolbool ) ) -> -> DeckDeck :
        :
        """List which cards in hand are playable this round"""
        """List which cards in hand are playable this round"""
        if if CardCard (( "♣""♣" , , "2""2" ) ) in in selfself .. handhand :
            :
            return return DeckDeck ([([ CardCard (( "♣""♣" , , "2""2" )])

        )])

        lead lead = = playedplayed [[ 00 ]] .. suit suit if if played played else else None
        None
        playable playable = = DeckDeck ([([ c c for for c c in in selfself .. hand hand if if cc .. suit suit == == leadlead ]) ]) or or selfself .. hand
        hand
        if if lead lead is is None None and and not not hearts_brokenhearts_broken :
            :
            playable playable = = DeckDeck ([([ c c for for c c in in playable playable if if cc .. suit suit != != "♡""♡" ])
        ])
        return return playable playable or or DeckDeck (( selfself .. handhand .. cardscards )

    )

    def def non_winning_cardsnon_winning_cards (( selfself , , playedplayed : : ListList [[ CardCard ], ], playableplayable : : DeckDeck ) ) -> -> DeckDeck :
        :
        """List playable cards that are guaranteed to not win the trick"""
        """List playable cards that are guaranteed to not win the trick"""
        if if not not playedplayed :
            :
            return return DeckDeck ([])

        ([])

        lead lead = = playedplayed [[ 00 ]] .. suit
        suit
        best_card best_card = = maxmax (( c c for for c c in in played played if if cc .. suit suit == == leadlead )
        )
        return return DeckDeck ([([ c c for for c c in in playable playable if if c c < < best_card best_card or or cc .. suit suit != != leadlead ])

    ])

    def def play_cardplay_card (( selfself , , playedplayed : : ListList [[ CardCard ], ], hearts_brokenhearts_broken : : boolbool ) ) -> -> CardCard :
        :
        """Play a card from a cpu player's hand"""
        """Play a card from a cpu player's hand"""
        playable playable = = selfself .. playable_cardsplayable_cards (( playedplayed , , hearts_brokenhearts_broken )
        )
        non_winning non_winning = = selfself .. non_winning_cardsnon_winning_cards (( playedplayed , , playableplayable )

        )

        # Strategy
        # Strategy
        if if non_winningnon_winning :
            :
            # Highest card not winning the trick, prefer points
            # Highest card not winning the trick, prefer points
            card card = = maxmax (( non_winningnon_winning , , keykey == lambda lambda cc : : (( cc .. pointspoints , , cc .. valuevalue ))
        ))
        elif elif lenlen (( playedplayed ) ) < < 33 :
            :
            # Lowest card maybe winning, avoid points
            # Lowest card maybe winning, avoid points
            card card = = minmin (( playableplayable , , keykey == lambda lambda cc : : (( cc .. pointspoints , , cc .. valuevalue ))
        ))
        elseelse :
            :
            # Highest card guaranteed winning, avoid points
            # Highest card guaranteed winning, avoid points
            card card = = maxmax (( playableplayable , , keykey == lambda lambda cc : : (( -- cc .. pointspoints , , cc .. valuevalue ))
        ))
        selfself .. handhand .. cardscards .. removeremove (( cardcard )
        )
        printprint (( ff "" {self.name}{self.name}  ->  ->  {card}{card} "" )
        )
        return return card

    card

    def def has_cardhas_card (( selfself , , cardcard : : CardCard ) ) -> -> boolbool :
        :
        return return card card in in selfself .. hand

    hand

    def def __repr____repr__ (( selfself ) ) -> -> strstr :
        :
        return return ff "" {self.__class__.__name__}{self.__class__.__name__} (( {self.name!r}{self.name!r} , ,  {self.hand}{self.hand} )"

)"

class class HumanPlayerHumanPlayer (( PlayerPlayer ):
    ):
    def def play_cardplay_card (( selfself , , playedplayed : : ListList [[ CardCard ], ], hearts_brokenhearts_broken : : boolbool ) ) -> -> CardCard :
        :
        """Play a card from a human player's hand"""
        """Play a card from a human player's hand"""
        playable playable = = sortedsorted (( selfself .. playable_cardsplayable_cards (( playedplayed , , hearts_brokenhearts_broken ))
        ))
        p_str p_str = = "  ""  " .. joinjoin (( ff "" {n}{n} : :  {c}{c} " " for for nn , , c c in in enumerateenumerate (( playableplayable ))
        ))
        np_str np_str = = " "" " .. joinjoin (( reprrepr (( cc ) ) for for c c in in selfself .. hand hand if if c c not not in in playableplayable )
        )
        printprint (( ff "  "   {p_str}{p_str}   (Rest:   (Rest:  {np_str}{np_str} )")" )
        )
        while while TrueTrue :
            :
            trytry :
                :
                card_num card_num = = intint (( inputinput (( ff "  "   {self.name}{self.name} , choose card: ", choose card: " ))
                ))
                card card = = playableplayable [[ card_numcard_num ]
            ]
            except except (( ValueErrorValueError , , IndexErrorIndexError ):
                ):
                pass
            pass
            elseelse :
                :
                break
        break
        selfself .. handhand .. playplay (( cardcard )
        )
        printprint (( ff "" {self.name}{self.name}  =>  =>  {card}{card} "" )
        )
        return return card

card

class class HeartsGameHeartsGame :
    :
    def def __init____init__ (( selfself , , ** namesnames : : strstr ) ) -> -> NoneNone :
        :
        selfself .. names names = = (( listlist (( namesnames ) ) + + "P1 P2 P3 P4""P1 P2 P3 P4" .. splitsplit ())[:())[: 44 ]
        ]
        selfself .. players players = = [[ PlayerPlayer (( nn ) ) for for n n in in selfself .. namesnames [[ 11 :]]
        :]]
        selfself .. playersplayers .. appendappend (( HumanPlayerHumanPlayer (( selfself .. namesnames [[ 00 ]))

    ]))

    def def playplay (( selfself ) ) -> -> NoneNone :
        :
        """Play a game of Hearts until one player go bust"""
        """Play a game of Hearts until one player go bust"""
        score score = = CounterCounter ({({ nn : : 0 0 for for n n in in selfself .. namesnames })
        })
        while while allall (( s s < < 100 100 for for s s in in scorescore .. valuesvalues ()):
            ()):
            printprint (( "" nn Starting new round:"Starting new round:" )
            )
            round_score round_score = = selfself .. play_roundplay_round ()
            ()
            scorescore .. updateupdate (( CounterCounter (( round_scoreround_score ))
            ))
            printprint (( "Scores:""Scores:" )
            )
            for for namename , , total_score total_score in in scorescore .. most_commonmost_common (( 44 ):
                ):
                printprint (( ff "" {name:<15}{name:<15}    {round_score[name]:>3}{round_score[name]:>3}    {total_score:>3}{total_score:>3} "" )

        )

        winners winners = = [[ n n for for n n in in selfself .. names names if if scorescore [[ nn ] ] == == minmin (( scorescore .. valuesvalues ())]
        ())]
        printprint (( ff "" nn {' and '.join(winners)} won the game"{' and '.join(winners)} won the game" )

    )

    def def play_roundplay_round (( selfself ) ) -> -> DictDict [[ strstr , , intint ]:
        ]:
        """Play a round of the Hearts card game"""
        """Play a round of the Hearts card game"""
        deck deck = = DeckDeck .. createcreate (( shuffleshuffle == TrueTrue )
        )
        for for playerplayer , , hand hand in in zipzip (( selfself .. playersplayers , , deckdeck .. dealdeal (( 44 )):
            )):
            playerplayer .. handhand .. add_cardsadd_cards (( handhand .. cardscards )
        )
        start_player start_player = = nextnext (
            (
            p p for for p p in in selfself .. players players if if pp .. has_cardhas_card (( CardCard (( "♣""♣" , , "2""2" ))
        ))
        )
        )
        tricks tricks = = {{ pp .. namename : : DeckDeck ([]) ([]) for for p p in in selfself .. playersplayers }
        }
        hearts hearts = = False

        False

        # Play cards from each player's hand until empty
        # Play cards from each player's hand until empty
        while while start_playerstart_player .. handhand :
            :
            playedplayed : : ListList [[ CardCard ] ] = = []
            []
            turn_order turn_order = = selfself .. player_orderplayer_order (( startstart == start_playerstart_player )
            )
            for for player player in in turn_orderturn_order :
                :
                card card = = playerplayer .. play_cardplay_card (( playedplayed , , hearts_brokenhearts_broken == heartshearts )
                )
                playedplayed .. appendappend (( cardcard )
            )
            start_player start_player = = selfself .. trick_winnertrick_winner (( playedplayed , , turn_orderturn_order )
            )
            trickstricks [[ start_playerstart_player .. namename ]] .. add_cardsadd_cards (( playedplayed )
            )
            printprint (( ff "" {start_player.name}{start_player.name}  wins the trick wins the trick nn "" )
            )
            hearts hearts = = hearts hearts or or anyany (( cc .. suit suit == == "♡" "♡" for for c c in in playedplayed )
        )
        return return selfself .. count_pointscount_points (( trickstricks )

    )

    def def player_orderplayer_order (( selfself , , startstart : : OptionalOptional [[ PlayerPlayer ] ] = = NoneNone ) ) -> -> ListList [[ PlayerPlayer ]:
        ]:
        """Rotate player order so that start goes first"""
        """Rotate player order so that start goes first"""
        if if start start is is NoneNone :
            :
            start start = = randomrandom .. choicechoice (( selfself .. playersplayers )
        )
        start_idx start_idx = = selfself .. playersplayers .. indexindex (( startstart )
        )
        return return selfself .. playersplayers [[ start_idxstart_idx :] :] + + selfself .. playersplayers [:[: start_idxstart_idx ]

    ]

    @staticmethod
    @staticmethod
    def def trick_winnertrick_winner (( tricktrick : : ListList [[ CardCard ], ], playersplayers : : ListList [[ PlayerPlayer ]) ]) -> -> PlayerPlayer :
        :
        lead lead = = tricktrick [[ 00 ]] .. suit
        suit
        valid valid = = [
            [
            (( cc .. valuevalue , , pp ) ) for for cc , , p p in in zipzip (( tricktrick , , playersplayers ) ) if if cc .. suit suit == == lead
        lead
        ]
        ]
        return return maxmax (( validvalid )[)[ 11 ]

    ]

    @staticmethod
    @staticmethod
    def def count_pointscount_points (( trickstricks : : DictDict [[ strstr , , DeckDeck ]) ]) -> -> DictDict [[ strstr , , intint ]:
        ]:
        return return {{ nn : : sumsum (( cc .. points points for for c c in in cardscards ) ) for for nn , , cards cards in in trickstricks .. itemsitems ()}

()}

if if __name__ __name__ == == "__main__""__main__" :
    :
    # Read player names from the command line
    # Read player names from the command line
    player_names player_names = = syssys .. argvargv [[ 11 :]
    :]
    game game = = HeartsGameHeartsGame (( ** player_namesplayer_names )
    )
    gamegame .. playplay ()
()

Here are a few points to note in the code:

以下是代码中需要注意的几点:

  • For type relationships that are hard to express using Union or type variables, you can use the @overload decorator. See Deck.__getitem__() for an example and the documentation for more information.

  • Subclasses correspond to subtypes, so that a HumanPlayer can be used wherever a Player is expected.

  • When a subclass reimplements a method from a superclass, the type annotations must match. See HumanPlayer.play_card() for an example.

  • 对于使用Union或类型变量难以表达的类型关系,可以使用@overload装饰器。 有关示例,请参见Deck.__getitem__() 。有关更多信息,请参见文档

  • 子类与子类型相对应,因此可以在需要Player任何地方使用HumanPlayer

  • 当子类从超类重新实现方法时,类型注释必须匹配。 有关示例,请参见HumanPlayer.play_card()

When starting the game, you control the first player. Enter numbers to choose which cards to play. The following is an example of game play, with the highlighted lines showing where the player made a choice:

开始游戏时,您控制第一个玩家。 输入数字以选择要玩的卡。 以下是游戏的示例,突出显示的行显示了玩家做出选择的位置:

静态类型检查 (Static Type Checking)

So far you have seen how to add type hints to your code. In this section you’ll learn more about how to actually perform static type checking of Python code.

到目前为止,您已经了解了如何在代码中添加类型提示。 在本节中,您将了解有关如何实际执行Python代码的静态类型检查的更多信息。

Mypy项目 (The Mypy Project)

Mypy was started by Jukka Lehtosalo during his Ph.D. studies at Cambridge around 2012. Mypy was originally envisioned as a Python variant with seamless dynamic and static typing. See Jukka’s slides from PyCon Finland 2012 for examples of the original vision of Mypy.

Mypy由Jukka Lehtosalo在其博士学位期间创立。 Mypy于2012年左右在剑桥学习。Mypy最初被设想为具有动态和静态无缝键入的Python变体。 有关Mypy原始愿景的示例,请参见Jukka的PyCon Finland 2012幻灯片

Most of those original ideas still play a big part in the Mypy project. In fact, the slogan “Seamless dynamic and static typing” is still prominently visible on Mypy’s home page and describes the motivation for using type hints in Python well.

大多数原始创意在Mypy项目中仍然发挥着重要作用。 实际上, 在Mypy的主页上仍显着标语“无缝动态和静态键入”,并很好地描述了在Python中使用类型提示的动机。

The biggest change since 2012 is that Mypy is no longer a variant of Python. In its first versions Mypy was a stand-alone language that was compatible with Python except for its type declarations. Following a suggestion by Guido van Rossum, Mypy was rewritten to use annotations instead. Today Mypy is a static type checker for regular Python code.

自2012年以来最大的变化是Mypy不再是Python的变体。 Mypy在其最初的版本中是一种独立的语言,除了其类型声明外,它与Python兼容。 根据Guido van Rossum建议 ,Mypy被改写为使用注释。 今天,Mypy是常规Python代码的静态类型检查器。

运行Mypy (Running Mypy)

Before running Mypy for the first time, you must install the program. This is most easily done using pip:

首次运行Mypy之前,必须安装该程序。 使用pip最容易做到这一点:

 $ pip install mypy
$ pip install mypy

With Mypy installed, you can run it as a regular command line program:

安装Mypy后,您可以将其作为常规命令行程序运行:

Running Mypy on your my_program.py Python file will check it for type errors without actually executing the code.

my_program.py Python文件上运行Mypy将检查它是否存在类型错误,而无需实际执行代码。

There are many available options when type checking your code. As Mypy is still under very active development, command line options are liable to change between versions. You should refer to Mypy’s help to see which settings are default on your version:

类型检查代码时,有许多可用选项。 由于Mypy仍处于非常活跃的开发中,因此命令行选项可能会在版本之间进行更改。 您应该参考Mypy的帮助以查看您的版本默认的设置:

 $ mypy --help
$ mypy --help
usage: mypy [-h] [-v] [-V] [more options; see below]
usage: mypy [-h] [-v] [-V] [more options; see below]
            [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]

            [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]

Mypy is a program that will type check your Python code.

Mypy is a program that will type check your Python code.

[... The rest of the help hidden for brevity ...]
[... The rest of the help hidden for brevity ...]

Additionally, the Mypy command line documentation online has a lot of information.

此外, 在线Mypy命令行文档中有很多信息。

Let’s look at some of the most common options. First of all, if you are using third-party packages without type hints, you may want to silence Mypy’s warnings about these. This can be done with the --ignore-missing-imports option.

让我们看一些最常见的选项。 首先,如果您正在使用没有类型提示的第三方软件包,则可能要使Mypy关于这些的警告保持沉默。 这可以通过--ignore-missing-imports选项来完成。

The following example uses Numpy to calculate and print the cosine of several numbers:

下面的示例使用Numpy来计算和打印多个数字的余弦:

Note that np.printoptions() is only available in version 1.15 and later of Numpy. Running this example prints some numbers to the console:

请注意, np.printoptions()仅在Numpy的1.15版和更高版本中可用。 运行此示例会将一些数字打印到控制台:

 $ python cosine.py
$ python cosine.py
[ 1.     0.707  0.    -0.707 -1.    -0.707 -0.     0.707  1.   ]
[ 1.     0.707  0.    -0.707 -1.    -0.707 -0.     0.707  1.   ]

The actual output of this example is not important. However, you should note that the argument x is annotated with np.ndarray on line 5, as we want to print the cosine of a full array of numbers.

此示例的实际输出并不重要。 但是,您应该注意,参数x在第5行上用np.ndarray进行了注释,因为我们要打印完整数字数组的余弦值。

You can run Mypy on this file as usual:

您可以照常在此文件上运行Mypy:

These warnings may not immediately make much sense to you, but you’ll learn about stubs and typeshed soon. You can essentially read the warnings as Mypy saying that the Numpy package does not contain type hints.

这些警告可能对您而言并不是很有意义,但是您将了解有关存根的信息并尽快进行排版 。 实际上,您可以将警告读为Mypy,即Numpy软件包不包含类型提示。

In most cases, missing type hints in third-party packages is not something you want to be bothered with so you can silence these messages:

在大多数情况下,不需要烦扰第三方程序包中的类型提示,因此可以使这些消息静音:

 $ mypy --ignore-missing-imports cosine.py 
$ mypy --ignore-missing-imports cosine.py 
$
$

If you use the --ignore-missing-import command line option, Mypy will not try to follow or warn about any missing imports. This might be a bit heavy-handed though, as it also ignores actual mistakes, like misspelling the name of a package.

如果使用--ignore-missing-import命令行选项,Mypy将不会尝试跟踪或警告任何丢失的导入 。 但是,这可能有点笨拙,因为它也忽略了实际错误,例如拼写错误的软件包名称。

Two less intrusive ways of handling third-party packages are using type comments or configuration files.

处理第三方软件包的两种不那么麻烦的方法是使用类型注释或配置文件。

In a simple example as the one above, you can silence the numpy warning by adding a type comment to the line containing the import:

在上面的一个简单示例中,您可以通过在包含导入的行中添加类型注释来使numpy警告静音:

The literal # type: ignore tells Mypy to ignore the import of Numpy.

文字# type: ignore告诉Mypy忽略Numpy的导入。

If you have several files, it might be easier to keep track of which imports to ignore in a configuration file. Mypy reads a file called mypy.ini in the current directory if it is present. This configuration file must contain a section called [mypy] and may contain module specific sections of the form [mypy-module].

如果您有多个文件,则可能更容易跟踪配置文件中要忽略的导入。 如果存在, mypy.ini在当前目录中读取一个名为mypy.ini的文件。 此配置文件必须包含名为[mypy]部分,并且可以包含[mypy-module]形式的模块特定的部分。

The following configuration file will ignore that Numpy is missing type hints:

以下配置文件将忽略Numpy缺少类型提示:

 # mypy.ini

# mypy.ini

[mypy]

[mypy]

[mypy-numpy]
[mypy-numpy]
ignore_missing_imports ignore_missing_imports = = True
True

There are many options that can be specified in the configuration file. It is also possible to specify a global configuration file. See the documentation for more information.

在配置文件中可以指定许多选项。 也可以指定一个全局配置文件。 有关更多信息,请参见文档

添加存根 (Adding Stubs)

Type hints are available for all the packages in the Python standard library. However, if you are using third-party packages you’ve already seen that the situation can be different.

类型提示可用于Python标准库中的所有软件包。 但是,如果您使用的是第三方软件包,您已经发现情况可能有所不同。

The following example uses the Parse package to do simple text parsing. To follow along you should first install Parse:

下面的示例使用Parse包进行简单的文本解析。 要继续进行,您应该首先安装Parse:

Parse can be used to recognize simple patterns. Here is a small program that tries its best to figure out your name:

解析可用于识别简单模式。 这是一个小程序,它会尽力找出您的名字:

 # parse_name.py

# parse_name.py

import import parse

parse

def def parse_nameparse_name (( texttext : : strstr ) ) -> -> strstr :
    :
    patterns patterns = = (
        (
        "my name is "my name is  {name}{name} "" ,
        ,
        "i'm "i'm  {name}{name} "" ,
        ,
        "i am "i am  {name}{name} "" ,
        ,
        "call me "call me  {name}{name} "" ,
        ,
        "" {name}{name} "" ,
    ,
    )
    )
    for for pattern pattern in in patternspatterns :
        :
        result result = = parseparse .. parseparse (( patternpattern , , texttext )
        )
        if if resultresult :
            :
            return return resultresult [[ "name""name" ]
    ]
    return return ""

""

answer answer = = inputinput (( "What is your name? ""What is your name? " )
)
name name = = parse_nameparse_name (( answeranswer )
)
printprint (( ff "Hi "Hi  {name}{name} , nice to meet you!", nice to meet you!" )
)

The main flow is defined in the last three lines: ask for your name, parse the answer, and print a greeting. The parse package is called on line 14 in order to try to find a name based on one of the patterns listed on lines 7-11.

最后三行定义了主要流程:询问您的姓名,解析答案并打印问候语。 在第14行调用了parse包,以便尝试根据第7-11行列出的模式之一查找名称。

The program can be used as follows:

该程序可以如下使用:

Note that even though I answer I am Geir Arne, the program figures out that I am is not part of my name.

请注意,即使我回答I am Geir Arne ,程序也会指出I am不是我的名字的一部分。

Let’s add a small bug to the program, and see if Mypy is able to help us detect it. Change line 16 from return result["name"] to return result. This will return a parse.Result object instead of the string containing the name.

让我们在程序中添加一个小错误,看看Mypy是否能够帮助我们检测到它。 将第16行从return result["name"]更改为return result 。 这将返回一个parse.Result对象,而不是包含名称的字符串。

Next run Mypy on the program:

接下来在程序上运行Mypy:

 $ mypy parse_name.py 
$ mypy parse_name.py 
parse_name.py:3: error: Cannot find module named 'parse'
parse_name.py:3: error: Cannot find module named 'parse'
parse_name.py:3: note: (Perhaps setting MYPYPATH or using the
parse_name.py:3: note: (Perhaps setting MYPYPATH or using the
                       "--ignore-missing-imports" flag would help)
                       "--ignore-missing-imports" flag would help)

Mypy prints a similar error to the one you saw in the previous section: It doesn’t know about the parse package. You could try to ignore the import:

Mypy打印出与上一节中看到的错误类似的错误:它不知道parse包。 您可以尝试忽略导入:

Unfortunately, ignoring the import means that Mypy has no way of discovering the bug in our program. A better solution would be to add type hints to the Parse package itself. As Parse is open source you can actually add types to the source code and send a pull request.

不幸的是,忽略导入意味着Mypy无法在程序中发现错误。 更好的解决方案是将类型提示添加到Parse包本身。 由于Parse是开源的,因此您实际上可以在源代码中添加类型并发送请求请求。

Alternatively, you can add the types in a stub file. A stub file is a text file that contains the signatures of methods and functions, but not their implementations. Their main function is to add type hints to code that you for some reason can’t change. To show how this works, we will add some stubs for the Parse package.

或者,您可以在存根文件中添加类型。 存根文件是一个文本文件,其中包含方法和函数的签名,但不包含其实现。 它们的主要功能是在代码中添加出于某种原因您无法更改的类型提示。 为了展示其工作原理,我们将为Parse包添加一些存根。

First of all, you should put all your stub files inside one common directory, and set the MYPYPATH environment variable to point to this directory. On Mac and Linux you can set MYPYPATH as follows:

首先,应该将所有存根文件放在一个公共目录中,并将MYPYPATH环境变量设置为指向该目录。 在Mac和Linux上,可以如下设置MYPYPATH

 $ $ export export MYPYPATHMYPYPATH =/home/gahjelle/python/stubs
= /home/gahjelle/python/stubs

You can set the variable permanently by adding the line to your .bashrc file. On Windows you can click the start menu and search for environment variables to set MYPYPATH.

您可以通过将行添加到.bashrc文件中来永久设置变量。 在Windows上,您可以单击开始菜单并搜索环境变量以设置MYPYPATH

Next, create a file inside your stubs directory that you call parse.pyi. It must be named for the package that you are adding type hints for, with a .pyi suffix. Leave this file empty for now. Then run Mypy again:

接下来,在存根目录中创建一个名为parse.pyi 。 它必须为要为其添加类型提示的包命名,后缀为.pyi 。 现在将此文件留空。 然后再次运行Mypy:

If you have set everything up correctly, you should see this new error message. Mypy uses the new parse.pyi file to figure out which functions are available in the parse package. Since the stub file is empty, Mypy assumes that parse.parse() does not exist, and then gives the error you see above.

如果正确设置了所有内容,则应该看到此新错误消息。 Mypy使用新的parse.pyi文件来找出parse包中可用的功能。 由于存根文件为空,Mypy假定parse.parse()不存在,然后给出您在上面看到的错误。

The following example does not add types for the whole parse package. Instead it shows the type hints you need to add in order for Mypy to type check your use of parse.parse():

以下示例未为整个parse包添加类型。 相反,它显示了您需要添加的类型提示,以便Mypy键入检查您对parse.parse()

 # parse.pyi

# parse.pyi

from from typing typing import import AnyAny , , MappingMapping , , OptionalOptional , , SequenceSequence , , TupleTuple , , Union

Union

class class ResultResult :
    :
    def def __init____init__ (
        (
        selfself ,
        ,
        fixedfixed : : SequenceSequence [[ strstr ],
        ],
        namednamed : : MappingMapping [[ strstr , , strstr ],
        ],
        spansspans : : MappingMapping [[ intint , , TupleTuple [[ intint , , intint ]],
    ]],
    ) ) -> -> NoneNone : : ...
    ...
    def def __getitem____getitem__ (( selfself , , itemitem : : UnionUnion [[ intint , , strstr ]) ]) -> -> strstr : : ...
    ...
    def def __repr____repr__ (( selfself ) ) -> -> strstr : : ...

...

def def parseparse (
    (
    formatformat : : strstr ,
    ,
    stringstring : : strstr ,
    ,
    evaluate_resultevaluate_result : : bool bool = = ...... ,
    ,
    case_sensitivecase_sensitive : : bool bool = = ...... ,
,
) ) -> -> OptionalOptional [[ ResultResult ]: ]: ...
...

The ellipsis ... are part of the file, and should be written exactly as above. The stub file should only contain type hints for variables, attributes, functions, and methods, so the implementations should be left out and replaced by ... markers.

省略号...是文件的一部分,应完全按照上面的说明编写。 存根文件应仅包含变量,属性,函数和方法的类型提示,因此应省略实现并用...标记代替。

Finally Mypy is able to spot the bug we introduced:

最终Mypy能够发现我们引入的错误:

This points straight to line 16 and the fact that we return a Result object and not the name string. Change return result back to return result["name"], and run Mypy again to see that it’s happy.

这直接指向第16行,这是我们返回Result对象而不是名称字符串的事实。 将return resultreturn result["name"] ,然后再次运行Mypy以查看它是否满意。

打字 (Typeshed)

You’ve seen how to use stubs to add type hints without changing the source code itself. In the previous section we added some type hints to the third-party Parse package. Now, it wouldn’t be very effective if everybody needs to create their own stubs files for all third-party packages they are using.

您已经了解了如何使用存根添加类型提示而不更改源代码本身。 在上一节中,我们向第三方Parse包中添加了一些类型提示。 现在,如果每个人都需要为他们使用的所有第三方程序包创建自己的存根文件,那将不是很有效。

Typeshed is a Github repository that contains type hints for the Python standard library, as well as many third-party packages. Typeshed comes included with Mypy so if you are using a package that already has type hints defined in Typeshed, the type checking will just work.

Typeshed是一个Github存储库,其中包含Python标准库的类型提示以及许多第三方程序包。 Mypy附带有Typeshed,因此,如果您使用的包装中已经在Typeshed中定义了类型提示,则类型检查就可以了。

You can also contribute type hints to Typeshed. Make sure to get the permission of the owner of the package first though, especially because they might be working on adding type hints into the source code itself—which is the preferred approach.

您还可以为Typeshed提供类型提示 。 不过,请确保首先获得包所有者的许可,尤其是因为他们可能正在努力在源代码本身中添加类型提示-这是首选方法

其他静态类型检查器 (Other Static Type Checkers)

In this tutorial, we have mainly focused on type checking using Mypy. However, there are other static type checkers in the Python ecosystem.

在本教程中,我们主要集中在使用Mypy进行类型检查。 但是,Python生态系统中还有其他静态类型检查器。

The PyCharm editor comes with its own type checker included. If you are using PyCharm to write your Python code, it will be automatically type checked.

PyCharm编辑器附带了自己的类型检查器。 如果您使用PyCharm编写Python代码,则会自动进行类型检查。

Facebook has developed Pyre. One of its stated goals is to be fast and performant. While there are some differences, Pyre functions mostly similar to Mypy. See the documentation if you’re interested in trying out Pyre.

Facebook开发了Pyre 。 其既定目标之一是要快速且高效。 尽管存在一些差异,但是Pyre的功能大部分类似于Mypy。 如果您有兴趣尝试使用Pyre,请参阅文档

在运行时使用类型 (Using Types at Runtime)

As a final note, it’s possible to use type hints also at runtime during execution of your Python program. Runtime type checking will probably never be natively supported in Python.

最后一点,可以在运行Python程序的同时在运行时使用类型提示。 Python可能永远不会原生支持运行时类型检查。

However, the type hints are available at runtime in the __annotations__ dictionary, and you can use those to do type checks if you desire. Before you run off and write your own package for enforcing types, you should know that there are already several packages doing this for you. Have a look at Enforce, Pydantic, or Pytypes for some examples.

但是,类型提示可在运行时在__annotations__词典中使用,并且您可以根据需要使用这些提示进行类型检查。 在开始编写自己的用于执行类型的程序包之前,您应该知道已经有多个程序包为您执行此操作。 请看一些示例的EnforcePydanticPytypes

Another use of type hints is for translating your Python code to C and compiling it for optimization. The popular Cython project uses a hybrid C/Python language to write statically typed Python code. However, since version 0.27 Cython has also supported type annotations. Recently, the Mypyc project has become available. While not yet ready for general use, it can compile some type annotated Python code to C extensions.

类型提示的另一种用法是将Python代码转换为C并进行编译以进行优化。 受欢迎的Cython项目使用C / Python混合语言来编写静态类型的Python代码。 但是,自版本0.27以来,Cython还支持类型注释。 最近, Mypyc项目已经可用。 虽然尚未准备好通用,但它可以将某些带类型注释的Python代码编译为C扩展。

结论 (Conclusion)

Type hinting in Python is a very useful feature that you can happily live without. Type hints don’t make you capable of writing any code you can’t write without using type hints. Instead, using type hints makes it easier for you to reason about code, find subtle bugs, and maintain a clean architecture.

Python中的类型提示是一项非常有用的功能,您可以愉快地使用它。 类型提示不能使您能够编写任何不使用类型提示就无法编写的代码。 相反,使用类型提示可以使您更轻松地进行代码推理,发现细微的错误并维护干净的体系结构。

In this tutorial you have learned how type hinting works in Python, and how gradual typing make type checks in Python more flexible than in many other languages. You’ve seen some of the pros and cons of using type hints, and how they can be added to code using annotations or type comments. Finally you saw many of the different types that Python supports, as well as how to perform static type checking.

在本教程中,您了解了类型提示在Python中如何工作,以及渐进式输入如何使Python中的类型检查比许多其他语言更灵活。 您已经了解了使用类型提示的利弊,以及如何使用注释或类型注释将它们添加到代码中。 最终,您了解了Python支持的许多不同类型,以及如何执行静态类型检查。

There are many resources to learn more about static type checking in Python. PEP 483 and PEP 484 give a lot of background about how type checking is implemented in Python. The Mypy documentation has a great reference section detailing all the different types available.

有很多资源可以了解有关Python中静态类型检查的更多信息。 PEP 483PEP 484提供了许多有关如何在Python中实现类型检查的背景知识。 Mypy文档有一个很好的参考部分,详细介绍了所有可用的类型。

翻译自: https://www.pybloggers.com/2019/01/the-ultimate-guide-to-python-type-checking/

python 学习指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值