当前位置: 首页 > news >正文

做网站的大公司有哪些linux服务器下如何新建网站

做网站的大公司有哪些,linux服务器下如何新建网站,网站建设与维护课程总结,制作视频的app有哪些正文共#xff1a;30429 字预计阅读时间#xff1a;76分钟原文链接:https://realpython.com/python-type-checking/作者:Geir Arne Hjelle译者#xff1a;陈祥安在本指南中#xff0c;你将了解Python类型检查。传统上#xff0c;Python解释器以灵活但隐式的方式处理类型。…正文共30429 字预计阅读时间76分钟原文链接:https://realpython.com/python-type-checking/作者:Geir Arne Hjelle译者陈祥安在本指南中你将了解Python类型检查。传统上Python解释器以灵活但隐式的方式处理类型。Python的最新版本允许你指定可由不同工具使用的显式类型提示以帮助您更有效地开发代码。通过本教程你将学到以下内容类型注解和提示(Type annotations and type hints)代码里添加静态类型静态类型检查运行时强制类型一致这是一个全面的指南将涵盖很多领域。如果您只是想快速了解一下类型提示在Python中是如何工作的并查看类型检查是否包括在您的代码中那么您不需要阅读全部内容。Hello Types和正反两部分将让您大致了解类型检查是如何工作的并介绍它在什么时候有用。Type Systems所有的编程语言都包括某种类型的系统该系统将它可以处理的对象类别以及如何处理这些类别形式化。例如类型系统可以定义一个数字类型其中42是数字类型对象的一个例子。动态类型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 raisedTypeError: unsupported operand type(s) for : int and str在上面例子中if从未运行过因此它未被类型检查过。else部分当计算1 “2”时因为类型不一致所以会产生一个类型错误。如果改变一个变量的值的类型 thing Hello type(thing) thing 28.1 type(thing)type()返回对象的类型。这些示例确认允许更改事物的类型并且Python在更改时正确地推断出类型。静态类型与动态类型相反的是静态类型。在不运行程序的情况下执行静态类型检查。在大多数静态类型语言中编译是在程序时完成的。例如C和Java对于静态类型通常不允许变量改变类型尽管可能存在将变量转换为不同类型的机制。让我们看一个静态类型语言的快速示例。请考虑以下Java代码段String thing;thing Hello;第一行声明thing的类型是String所以后面的赋值也必须指定字符串类型如果你给thing2就会出错但是python就不会出错。虽然Python始终是一种动态类型语言。但是PEP 484引入了类型提示这使得还可以对Python代码进行静态类型检查。与大多数其他静态类型语言中的工作方式不同类型提示本身不会导致Python强制执行类型。顾名思义键入提示只是建议类型。鸭子类型在谈论Python时经常使用的另一个术语是鸭子打字。这个绰号来自短语“如果它像鸭子一样行走它像鸭子一样嘎嘎叫那它一定是鸭子”(或其任何变化)。鸭子类型是一个与动态类型相关的概念其中对象的类型或类不如它定义的方法重要。使用鸭子类型根本不需要检查类型而是检查给定方法或属性是否存在。下面一个例子, 你可在python所有的对象中使用 len() 的魔法函数__len__() 方法: class TheHobbit:... def __len__(self):... return 95022... the_hobbit TheHobbit() len(the_hobbit)95022实际len()方法就是下面的这种方法实现的:def len(obj):return obj.__len__()由此发现对象也可以像strlistdict那样使用len方法只不过需要重新写__len__魔法函数即可。Hello Types在本节中您将看到如何向函数添加类型提示。下面的函数通过添加适当的大写字母和装饰线将文本字符串转换为标题def headline(text, alignTrue):if align:return f{text.title()}\n{- * len(text)}else:return f {text.title()} .center(50, o)默认情况下函数返回与下划线对齐的左侧标题。通过将align标志设置为False您还可以选择使用o围绕字符串: print(headline(python type checking))Python Type Checking-------------------- print(headline(python type checking, alignFalse))oooooooooooooo Python Type Checking oooooooooooooo是时候给我们第一个类型提示了要向函数中添加关于类型的信息只需如下注释其参数和返回值def headline(text: str, align: bool True) - str:...text: str 意思是text值类型是str 类似的, 可选参数 align 指定其类型为bool并给定默认值True. 最后, - str 表示函数headline() 返回值类型为str。在代码风格方面PEP 8建议如下:对冒号使用常规规则即冒号前没有空格冒号后面有一个空格textstr。将参数注释与默认值组合时在符号周围使用空格alignbool True。def  headline(...) - str使用空格围绕。 print(headline(python type checking, alignleft))Python Type Checking--------------------但是如果传入的参数类型不是指定的参数类型程序不会出现错误此时可以使用类型检查模块通过提示内容确定是否类型输入正确如mypy。你可以通过 pip安装:$ pip install mypy将以下代码放在名为headlines.py的文件中:#headlines.pydef headline(text: str, align: bool  True) - str:if align:return f{text.title()}\n{- * len(text)}else:return f {text.title()} .center(50, o)print(headline(python type checking))print(headline(use mypy, aligncenter))然后通过mypy运行上面的文件:$mypy headlines.pyheadlines.py:10: error: Argument align to headline has incompatibletype str; expected bool根据类型提示Mypy能够告诉我们我们在第10行使用了错误的类型这样说明一个问题参数名align不是很好确定参数是bool类型我们将代码改成下面这样换一个识别度高的参数名centered。#headlines.pydef headline(text: str, centered: bool  False):if not centered:return f{text.title()}\n{- * len(text)}else:return f {text.title()} .center(50, o)print(headline(python type checking))print(headline(use mypy, centeredTrue))再次运行文件发现没有错误提示ok。$ mypy headlines.py$然后就可以打印结果了$python headlines.pyPython Type Checking--------------------oooooooooooooooooooo Use Mypy oooooooooooooooooooo第一个标题与左侧对齐而第二个标题居中。Pros and Cons类型提示的增加方便了IDE的代码提示功能我们看到下面text使用.即可得到str使用的一些方法和熟悉。类型提示可帮助您构建和维护更清晰的体系结构。编写类型提示的行为迫使您考虑程序中的类型。虽然Python的动态特性是其重要资产之一但是有意识地依赖于鸭子类型重载方法或多种返回类型是一件好事。需要注意的是类型提示会在启动时带来轻微的损失。如果您需要使用类型模块那么导入时间可能很长尤其是在简短的脚本中。那么您应该在自己的代码中使用静态类型检查吗这不是一个全有或全无的问题。幸运的是Python支持渐进式输入的概念。这意味着您可以逐渐在代码中引入类型。没有类型提示的代码将被静态类型检查器忽略。因此您可以开始向关键组件添加类型只要它能为您增加价值就可以继续。关于是否向项目添加类型的一些经验法则如果您刚开始学习Python可以安全地等待类型提示直到您有更多经验。类型提示在短暂抛出脚本中增加的价值很小。在其他人使用的库中尤其是在PyPI上发布的库中类型提示会增加很多价值。使用库的其他代码需要这些类型提示才能正确地进行类型检查。在较大的项目中类型提示可以帮助您理解类型是如何在代码中流动的强烈建议您这样做。在与他人合作的项目中更是如此。Bernat Gabor在他的文章《Python中类型提示的状态》中建议只要值得编写单元测试就应该使用类型提示。实际上类型提示在代码中扮演着类似于测试的角色:它们帮助开发人员编写更好的代码。注解Python 3.0中引入了注释最初没有任何特定用途。它们只是将任意表达式与函数参数和返回值相关联的一种方法。多年以后PEP 484根据Jukka Lehtosalo博士项目Mypy所做的工作定义了如何向Python代码添加类型提示。添加类型提示的主要方法是使用注释。随着类型检查变得越来越普遍这也意味着注释应该主要保留给类型提示。接下来的章节将解释注释如何在类型提示的上下文中工作。函数注解之前我们也提到过函数的注解例子向下面这样:def func(arg: arg_type, optarg: arg_type default) - return_type:...对于参数语法是参数:注释而返回类型使用- 注释进行注释。请注意注释必须是有效的Python表达式。以下简单示例向计算圆周长的函数添加注释::import mathdef circumference(radius: float) - float:return 2 * math.pi * radius通调用circumference对象的__annotations__魔法函数可以输出函数的注解信息。 circumference(1.23)7.728317927830891 circumference.__annotations__{radius: , return: }有时您可能会对Mypy如何解释您的类型提示感到困惑。对于这些情况有一些特殊的Mypy表达式:reveal type()和reveal local()。您可以在运行Mypy之前将这些添加到您的代码中Mypy将报告它所推断的类型。例如将以下代码保存为reveal.py。#reveal.pyimport mathreveal_type(math.pi)radius 1circumference 2 * math.pi * radiusreveal_locals()然后通过mypy运行上面代码$mypy reveal.pyreveal.py:4: error: Revealed type is builtins.floatreveal.py:8: error: Revealed local types are:reveal.py:8: error: circumference: builtins.floatreveal.py:8: error: radius: builtins.int即使没有任何注释Mypy也正确地推断了内置数学的类型。以及我们的局部变量半径和周长。注意:以上代码需要通过mypy运行如果用python运行会报错另外mypy 版本不低于 0.610变量注解有时类型检查器也需要帮助来确定变量的类型。变量注释在PEP 526中定义并在Python 3.6中引入。语法与函数参数注释相同pi: float 3.142def circumference(radius: float) - float:return 2 * pi * radiuspi被声明为float类型。注意: 静态类型检查器能够很好地确定3.142是一个浮点数因此在本例中不需要pi的注释。随着您对Python类型系统的了解越来越多您将看到更多有关变量注释的示例。.变量注释存储在模块级__annotations__字典中: circumference(1)6.284 __annotations__{pi: }即使只是定义变量没有给赋值也可以通过__annotations__获取其类型。虽然在python中没有赋值的变量直接输出是错误的。 nothing: str nothingNameError: name nothing is not defined __annotations__{nothing: }类型注解如上所述注释是在Python 3中引入的并且它们没有被反向移植到Python 2.这意味着如果您正在编写需要支持旧版Python的代码则无法使用注释。要向函数添加类型注释您可以执行以下操作import mathdef circumference(radius):# type: (float) - floatreturn 2 * math.pi * radius类型注释只是注释所以它们可以用在任何版本的Python中。类型注释由类型检查器直接处理所以不存在__annotations__字典对象中: circumference.__annotations__{}类型注释必须以type: 字面量开头并与函数定义位于同一行或下一行。如果您想用几个参数来注释一个函数您可以用逗号分隔每个类型:def headline(text, width80, fill_char-):# type: (str, int, str) - strreturn f {text.title()} .center(width, fill_char)print(headline(type comments work, width40))您还可以使用自己的注释在单独的行上编写每个参数:#headlines.pydefheadline(text,           # type: strwidth80,       # type: intfill_char-,  # type: str):                  # type: (...) - strreturn f {text.title()} .center(width, fill_char)print(headline(type comments work, width40))通过Python和Mypy运行示例$python headlines.py---------- Type Comments Work ----------$mypy headline.py$如果传入一个字符串widthfull再次运行mypy会出现一下错误。$mypy headline.pyheadline.py:10: error: Argument width to headline has incompatibletype str; expected int您还可以向变量添加类型注释。这与您向参数添加类型注释的方式类似:pi  3.142  # type: float上面的例子可以检测出pi是float类型。So, Type Annotations or Type Comments?所以向自己的代码添加类型提示时应该使用注释还是类型注释简而言之:尽可能使用注释必要时使用类型注释。注释提供了更清晰的语法使类型信息更接近您的代码。它们也是官方推荐的写入类型提示的方式并将在未来进一步开发和适当维护。类型注释更详细可能与代码中的其他类型注释冲突如linter指令。但是它们可以用在不支持注释的代码库中。还有一个隐藏选项3:存根文件。稍后当我们讨论向第三方库添加类型时您将了解这些。存根文件可以在任何版本的Python中使用代价是必须维护第二组文件。通常如果无法更改原始源代码则只需使用存根文件。Playing With Python Types, Part 1到目前为止您只在类型提示中使用了strfloat和bool等基本类型。但是Python类型系统非常强大它可以支持多种更复杂的类型。在本节中您将了解有关此类型系统的更多信息同时实现简单的纸牌游戏。您将看到如何指定序列和映射的类型如元组列表和字典键入别名使代码更容易阅读该函数和方法不返回任何内容可以是任何类型的对象在简要介绍了一些类型理论之后您将看到更多用Python指定类型的方法。您可以在这里找到代码示例https://github.com/realpython/materials/tree/master/python-type-checkingExample: A Deck of Cards以下示例显示了一副常规纸牌的实现:#game.pyimport randomSUITS  ♠ ♡ ♢ ♣.split()RANKS  2 3 4 5 6 7 8 9 10 J Q K A.split()def create_deck(shuffleFalse):Create a new deck of 52 cardsdeck  [(s, r) for r in RANKS for s in SUITS]if shuffle:random.shuffle(deck)return deckdef deal_hands(deck):Deal the cards in the deck into four handsreturn (deck[0::4], deck[1::4], deck[2::4], deck[3::4])def play():Play a 4-player card gamedeck  create_deck(shuffleTrue)names  P1 P2 P3 P4.split()hands  {n: h for n, h in zip(names, deal_hands(deck))}for name, cards in hands.items():card_str   .join(f{s}{r} for (s, r) in cards)print(f{name}: {card_str})if __name__  __main__:play()每张卡片都表示为套装和等级的字符串元组。卡组表示为卡片列表。create_deck()创建一个由52张扑克牌组成的常规套牌并可选择随机播放这些牌。deal_hands()将牌组交给四名玩家。最后play()扮演游戏。截至目前它只是通过构建一个洗牌套牌并向每个玩家发牌来准备纸牌游戏。以下是典型输出$python game.pyP4: ♣9 ♢9 ♡2 ♢7 ♡7 ♣A ♠6 ♡K ♡5 ♢6 ♢3 ♣3 ♣QP1: ♡A ♠2 ♠10 ♢J ♣10 ♣4 ♠5 ♡Q ♢5 ♣6 ♠A ♣5 ♢4P2: ♢2 ♠7 ♡8 ♢K ♠3 ♡3 ♣K ♠J ♢A ♣7 ♡6 ♡10 ♠KP3: ♣2 ♣8 ♠8 ♣J ♢Q ♡9 ♡J ♠4 ♢8 ♢10 ♠9 ♡4 ♠Q下面让我一步一步对上面的代码进行拓展。Sequences and Mappings让我们为我们的纸牌游戏添加类型提示。换句话说让我们注释函数create_deck()deal_hands()和play()。第一个挑战是你需要注释复合类型例如用于表示卡片组的列表和用于表示卡片本身的元组。对于像str、float和bool这样的简单类型添加类型提示就像使用类型本身一样简单: name: str Guido pi: float 3.142 centered: bool False对于复合类型可以执行相同的操作: names: list [Guido, Jukka, Ivan] version: tuple (3, 7, 1) options: dict {centered: False, capitalize: True}上面的注释还是不完善比如names我们只是知道这是list类型但是我们不知道list里面的元素数据类型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}需要注意的是这些类型中的每一个都以大写字母开头并且它们都使用方括号来定义项的类型:names是一个str类型的list数组。version是一个含有3个int类型的元组options 是一个字典键名类型str简直类型booltyping 还包括其他的很多类型比如 Counter, Deque, FrozenSet, NamedTuple, 和 Set.此外该模块还包括其他的类型你将在后面的部分中看到.让我们回到扑克游戏. 因为卡片是有2个str组成的元组定义的. 所以你可以写作Tuple[str, str],所以函数create_deck()返回值的类型就是 List[Tuple[str, str]].def create_deck(shuffle: bool  False) - List[Tuple[str, str]]:Create a new deck of 52 cardsdeck  [(s, r) for r in RANKS for s in SUITS]if shuffle:random.shuffle(deck)return deck除了返回值之外您还将bool类型添加到可选的shuffle参数中。注意: 元组和列表的声明是有区别的元组是不可变序列通常由固定数量的可能不同类型的元素组成。例如我们将卡片表示为套装和等级的元组。通常您为n元组编写元组[t_1t_2...t_n]。列表是可变序列通常由未知数量的相同类型的元素组成例如卡片列表。无论列表中有多少元素注释中只有一种类型List [t]。在许多情况下你的函数会期望某种顺序并不关心它是列表还是元组。在这些情况下您应该使用typing.Sequence在注释函数参数时from typing import List, Sequencedef square(elems: Sequence[float]) - List[float]:return [x**2 for x in elems]使用 Sequence 是一个典型的鸭子类型的例子. 也就意味着可以使用len() 和 .__getitem__()等方法。类型别名使用嵌套类型(如卡片组)时类型提示可能会变得非常麻烦。你可能需要仔细看List [Tuple [strstr]]才能确定它与我们的一副牌是否相符.现在考虑如何注释deal_hands():def deal_hands(deck: List[Tuple[str, str]]) - Tuple[List[Tuple[str, str]],List[Tuple[str, str]],List[Tuple[str, str]],List[Tuple[str, str]],]:Deal the cards in the deck into four handsreturn (deck[0::4], deck[1::4], deck[2::4], deck[3::4])这也太麻烦了!不怕我们还可以使用起别名的方式把注解的类型赋值给一个新的变量方便在后面使用就像下面这样:from typing import List, TupleCard Tuple[str, str]Deck List[Card]现在我们就可以使用别名对之前的代码进行注解了:def deal_hands(deck: Deck) - Tuple[Deck, Deck, Deck, Deck]:Deal the cards in the deck into four handsreturn (deck[0::4], deck[1::4], deck[2::4], deck[3::4])类型别名让我们的代码变的简洁了不少我们可以打印变量看里面具体的值: from typing import List, Tuple Card Tuple[str, str] Deck List[Card] Decktyping.List[typing.Tuple[str, str]]当输出Deck的时候可以看到其最终的类型.函数无返回值对于没有返回值的函数我们可以指定None:#play.pydef play(player_name: str) - None:print(f{player_name} plays)ret_val  play(Filip)通过mypy检测上面代码$mypy play.pyplay.py:6: error: play does not return a value作为一个更奇特的情况请注意您还可以注释从未期望正常返回的函数。这是使用NoReturn完成的:from typing import NoReturndef black_hole() - NoReturn:raise Exception(There is no going back ...)因为black_hole( )总是引发异常所以它永远不会正确返回。Example: Play Some Cards让我们回到我们的纸牌游戏示例。在游戏的第二个版本中我们像以前一样向每个玩家发放一张牌。然后选择一个开始玩家并且玩家轮流玩他们的牌。虽然游戏中没有任何规则所以玩家只会玩随机牌:#game.pyimport randomfrom typing import List, TupleSUITS  ♠ ♡ ♢ ♣.split()RANKS  2 3 4 5 6 7 8 9 10 J Q K A.split()Card  Tuple[str, str]Deck  List[Card]def create_deck(shuffle: bool  False) - Deck:Create a new deck of 52 cardsdeck  [(s, r) for r in RANKS for s in SUITS]if shuffle:random.shuffle(deck)return deckdef deal_hands(deck: Deck) - Tuple[Deck, Deck, Deck, Deck]:Deal the cards in the deck into four handsreturn (deck[0::4], deck[1::4], deck[2::4], deck[3::4])def choose(items):Choose and return a random itemreturn random.choice(items)def player_order(names, startNone):Rotate player order so that start goes firstif start is None:start  choose(names)start_idx  names.index(start)return names[start_idx:]  names[:start_idx]def play() - None:Play a 4-player card gamedeck  create_deck(shuffleTrue)names  P1 P2 P3 P4.split()hands  {n: h for n, h in zip(names, deal_hands(deck))}start_player  choose(names)turn_order  player_order(names, startstart_player)# Randomly play cards from each players hand until emptywhile hands[start_player]:for name in turn_order:card  choose(hands[name])hands[name].remove(card)print(f{name}: {card[0]  card[1]:3}  , end)print()if __name__  __main__:play()请注意除了更改play()之外我们还添加了两个需要类型提示的新函数choose()和player_order()。在讨论我们如何向它们添加类型提示之前以下是运行游戏的示例输出$python game.pyP3: ♢10 P4: ♣4 P1: ♡8 P2: ♡QP3: ♣8 P4: ♠6 P1: ♠5 P2: ♡KP3: ♢9 P4: ♡J P1: ♣A P2: ♡AP3: ♠Q P4: ♠3 P1: ♠7 P2: ♠AP3: ♡4 P4: ♡6 P1: ♣2 P2: ♠KP3: ♣K P4: ♣7 P1: ♡7 P2: ♠2P3: ♣10 P4: ♠4 P1: ♢5 P2: ♡3P3: ♣Q P4: ♢K P1: ♣J P2: ♡9P3: ♢2 P4: ♢4 P1: ♠9 P2: ♠10P3: ♢A P4: ♡5 P1: ♠J P2: ♢QP3: ♠8 P4: ♢7 P1: ♢3 P2: ♢JP3: ♣3 P4: ♡10 P1: ♣9 P2: ♡2P3: ♢6 P4: ♣6 P1: ♣5 P2: ♢8在该示例中随机选择玩家P3作为起始玩家。反过来每个玩家都会玩一张牌先是P3然后是P4然后是P1最后是P2。只要手中有任何左手玩家就会持续打牌。The Any Typechoose()适用于名称列表和卡片列表(以及任何其他序列)。为此添加类型提示的一种方法是import randomfrom typing import Any, Sequencedef choose(items: Sequence[Any]) - Any:return random.choice(items)这或多或少意味着它items是一个可以包含任何类型的项目的序列而choose()将返回任何类型的这样的项目。不是很严谨此时请考虑以下示例#choose.pyimport randomfrom typing import Any, Sequencedef choose(items: Sequence[Any]) - Any:return random.choice(items)names  [Guido, Jukka, Ivan]reveal_type(names)name  choose(names)reveal_type(name)虽然Mypy会正确推断名称是字符串列表但由于使用了任意类型在调用choose ( )后该信息会丢失:$mypy choose.pychoose.py:10: error: Revealed type is builtins.list[builtins.str*]choose.py:13: error: Revealed type is Any由此可以得知如果使用了Any使用mypy的时候将不容易检测。Playing With Python Types, Part 2import randomfrom typing import Any, Sequencedef choose(items: Sequence[Any]) - Any:return random.choice(items)使用Any的问题在于您不必要地丢失类型信息。您知道如果将一个字符串列表传递给choose()它将返回一个字符串。类型变量类型变量是一个特殊变量可以采用任何类型具体取决于具体情况。让我们创建一个有效封装choose()行为的类型变量#choose.pyimport randomfrom typing import Sequence, TypeVarChoosable  TypeVar(Chooseable)def choose(items: Sequence[Choosable]) - Choosable:return random.choice(items)names  [Guido, Jukka, Ivan]reveal_type(names)name  choose(names)reveal_type(name)类型变量必须使用类型模块中的TypeVar定义。使用时类型变量的范围覆盖所有可能的类型并获取最特定的类型。在这个例子中name现在是一个str$mypy choose.pychoose.py:12: error: Revealed type is builtins.list[builtins.str*]choose.py:15: error: Revealed type is builtins.str*考虑一些其他例子:#choose_examples.pyfrom choose import choosereveal_type(choose([Guido, Jukka, Ivan]))reveal_type(choose([1, 2, 3]))reveal_type(choose([True, 42, 3.14]))reveal_type(choose([Python, 3, 7])前两个例子应该有类型str和int但是后两个呢单个列表项有不同的类型在这种情况下可选择类型变量会尽最大努力适应:$mypy choose_examples.pychoose_examples.py:5: error: Revealed type is builtins.str*choose_examples.py:6: error: Revealed type is builtins.int*choose_examples.py:7: error: Revealed type is builtins.float*choose_examples.py:8: error: Revealed type is builtins.object*正如您已经看到的那样bool是int的子类型它也是float的子类型。所以在第三个例子中choose()的返回值保证可以被认为是浮点数。在最后一个例子中str和int之间没有子类型关系因此关于返回值可以说最好的是它是一个对象。请注意这些示例都没有引发类型错误。有没有办法告诉类型检查器选择( )应该同时接受字符串和数字但不能同时接受两者您可以通过列出可接受的类型来约束类型变量:#choose.pyimport randomfrom typing import Sequence, TypeVarChoosable  TypeVar(Choosable, str, float)def choose(items: Sequence[Choosable]) - Choosable:return random.choice(items)reveal_type(choose([Guido, Jukka, Ivan]))reveal_type(choose([1, 2, 3]))reveal_type(choose([True, 42, 3.14]))reveal_type(choose([Python, 3, 7]))现在Choosable只能是str或float而Mypy会注意到最后一个例子是一个错误$mypy choose.pychoose.py:11: error: Revealed type is builtins.str*choose.py:12: 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: Value of type variable Choosable of choosecannot be object还要注意在第二个例子中即使输入列表只包含int对象该类型也被认为是float类型的。这是因为Choosable仅限于str和floatint是float的一个子类型。在我们的纸牌游戏中我们想限制choose()只能用str和Card类型:Choosable TypeVar(Choosable, str, Card)def choose(items: Sequence[Choosable]) - Choosable:...我们简要地提到Sequence表示列表和元组。正如我们所指出的一个Sequence可以被认为是一个duck类型因为它可以是实现了.__ len __()和.__ getitem __()的任何对象。鸭子类型和协议回想一下引言中的以下例子::def len(obj):return obj.__len__()len()方法可以返回任何实现__len__魔法函数的对象的长度那我们如何在len()里添加类型提示尤其是参数obj的类型表示呢答案隐藏在学术术语structural subtyping背后。structural subtyping的一种方法是根据它们是normal的还是structural的:在normal系统中类型之间的比较基于名称和声明。Python类型系统大多是名义上的因为它们的子类型关系可以用int来代替float。在structural系统中类型之间的比较基于结构。您可以定义一个结构类型“大小”它包括定义的所有实例。__len_ _ _()无论其标称类型如何.目前正在通过PEP 544为Python带来一个成熟的结构类型系统该系统旨在添加一个称为协议的概念。尽管大多数PEP 544已经在Mypy中实现了。协议指定了一个或多个实现的方法。例如所有类定义。_ _ len _ _ _()完成typing.Sized协议。因此我们可以将len()注释如下:from typing import Sizeddef len(obj: Sized) - int:return obj.__len__()除此之外在Typing中还包括以下模块 Container, Iterable, Awaitable, 还有 ContextManager.你也可以声明自定的协议 通过导入typing_extensions模块中的Protocol协议对象然后写一个继承该方法的子类像下面这样:from typing_extensions import Protocolclass Sized(Protocol):def __len__(self) - int: ...def len(obj: Sized) - int:return obj.__len__()到写本文为止需要通过pip安装上面使用的第三方模块pip install typing-extensions.Optional 类型在python中有一种公共模式就是设置参数的默认值None这样做通常是为了避免可变默认值的问题或者让一个标记值标记特殊行为。在上面 的card 例子中, 函数 player_order() 使用 None 作为参数start的默认值,表示还没有指定玩家:def player_order(names, startNone):Rotate player order so that start goes firstif start is None:start  choose(names)start_idx  names.index(start)return names[start_idx:]  names[:start_idx]这给类型提示带来的挑战是通常start应该是一个字符串。但是它也可能采用特殊的非字符串值“None”。为解决上面的问题这里可以使用Optional类型:from typing import Sequence, Optionaldef player_order(names: Sequence[str], start: Optional[str] None) - Sequence[str]:...等价于Union类型的 Union[None, str]意思是这个参数的值类型为str默认的话可以是请注意使用Optional或Union时必须注意变量是否在后面有操作。比如上面的例子通过判断start是否为None。如果不判断None的情况在做静态类型检查的时候会发生错误1 # player_order.py23 from typing import Sequence, Optional45 def player_order(6 names: Sequence[str], start: Optional[str] None7 ) - Sequence[str]:8 start_idx names.index(start)9 return names[start_idx:] names[:start_idx]Mypy告诉你还没有处理start为None的情况。$mypy player_order.pyplayer_order.py:8: error: Argument 1 to index of list has incompatibletype Optional[str]; expected str也可以使用以下操作声明参数start的类型。def player_order(names: Sequence[str], start: str None) - Sequence[str]:...如果你不想 Mypy 出现报错你可以使用命令--no-implicit-optionalExample: The Object(ive) of the Game接下来我们会重写上面的扑克牌游戏让它看起来更面向对象以及适当的使用注解。将我们的纸牌游戏翻译成以下几个类 Card, Deck, Player, Game 下面是代码实现。#game.pyimport randomimport sysclass Card:SUITS  ♠ ♡ ♢ ♣.split()RANKS  2 3 4 5 6 7 8 9 10 J Q K A.split()def __init__(self, suit, rank):self.suit  suitself.rank  rankdef __repr__(self):return f{self.suit}{self.rank}class Deck:def __init__(self, cards):self.cards  cardsclassmethoddef create(cls, shuffleFalse):Create a new deck of 52 cardscards  [Card(s, r) for r in Card.RANKS for s in Card.SUITS]if shuffle:random.shuffle(cards)return cls(cards)def deal(self, num_hands):Deal the cards in the deck into a number of handscls  self.__class__return tuple(cls(self.cards[i::num_hands]) for i in range(num_hands))class Player:def __init__(self, name, hand):self.name  nameself.hand  handdef play_card(self):Play a card from the players handcard  random.choice(self.hand.cards)self.hand.cards.remove(card)print(f{self.name}: {card!r:3}  , end)return cardclass Game:def __init__(self, *names):Set up the deck and deal cards to 4 playersdeck  Deck.create(shuffleTrue)self.names  (list(names)  P1 P2 P3 P4.split())[:4]self.hands  {n: Player(n, h) for n, h in zip(self.names, deck.deal(4))}def play(self):Play a card gamestart_player  random.choice(self.names)turn_order  self.player_order(startstart_player)# Play cards from each players hand until emptywhile self.hands[start_player].hand.cards:for name in turn_order:self.hands[name].play_card()print()def player_order(self, startNone):Rotate player order so that start goes firstif start is None:start  random.choice(self.names)start_idx  self.names.index(start)return self.names[start_idx:]  self.names[:start_idx]if __name__  __main__:# Read player names from command lineplayer_names  sys.argv[1:]game  Game(*player_names)game.play()好了下面让我们添加注解Type Hints for Methods方法的类型提示与函数的类型提示非常相似。唯一的区别是self参数不需要注释因为它是一个类的实例。Card类的类型很容易添加:class Card:SUITS  ♠ ♡ ♢ ♣.split()RANKS  2 3 4 5 6 7 8 9 10 J Q K A.split()def __init__(self, suit: str, rank: str) - None:self.suit  suitself.rank  rankdef __repr__(self) - str:return f{self.suit}{self.rank}__init__() 的返回值总是为NoneClass作为类型类别和类型之间有对应关系。例如Card的所有实例一起形成Card类型。要使用类作为类型只需使用类的名称Card。例如Deck(牌组)本质上由一组Card对象组成你可以像下面这样去声明class Deck:def __init__(self, cards: List[Card]) - None:self.cards  cards但是当您需要引用当前定义的类时这种方法就不那么有效了。例如Deck.create() 类方法返回一个带有Deck类型的对象。但是您不能简单地添加- Deck因为Deck类还没有完全定义。这种情况下可以在注释中使用字符串文字。就像下面使用Deck,声明了返回类型然后加入docstring注释进一步说明方法。class Deck:classmethoddef create(cls, shuffle: bool  False) - Deck:Create a new deck of 52 cardscards  [Card(s, r) for r in Card.RANKS for s in Card.SUITS]if shuffle:random.shuffle(cards)return cls(cards)Player类也可以直接使用 Deck作为类型声明. 因为在前面我们已经定义它class Player:def __init__(self, name: str, hand: Deck) - None:self.name  nameself.hand  hand通常注释不会在运行时使用。这为推迟对注释的评估提供了动力。该提议不是将注释评估为Python表达式并存储其值而是存储注释的字符串表示形式并仅在需要时对其进行评估。这种功能计划在Python 4.0中成为标准。但是在Python 3.7及更高版本中可以通过导入__future__属性的annotations来实现from __future__ import annotationsclass Deck:classmethoddef create(cls, shuffle: bool False) - Deck:...使用 __future__之后就可以使用Deck对象替换字符串Deck了。返回 self 或者 cls如前所述通常不应该注释self或cls参数。在一定程度上这是不必要的因为self指向类的实例所以它将具有类的类型。在Card示例中self拥有隐式类型Card。此外显式地添加这种类型会很麻烦因为还没有定义该类。所以需要使用字符串“Card”声明返回类型。但是有一种情况可能需要注释self或cls。考虑如果你有一个其他类继承的超类并且有返回self或cls的方法会发生什么#dogs.pyfrom datetime import dateclass Animal:def __init__(self, name: str, birthday: date) - None:self.name nameself.birthday birthdayclassmethoddef newborn(cls, name: str) - Animal:return cls(name, date.today())def twin(self, name: str) - Animal:cls self.__class__return cls(name, self.birthday)class Dog(Animal):def bark(self) - None:print(f{self.name} says woof!)fido Dog.newborn(Fido)pluto fido.twin(Pluto)fido.bark()pluto.bark()运行上面的代码Mypy会抛出下面的错误:$mypy dogs.pydogs.py:24: error: Animal has no attribute barkdogs.py:25: error: Animal has no attribute bark问题是即使继承的Dog.newborn()和Dog.twin()方法将返回一个Dog注释表明它们返回一个Animal。在这种情况下您需要更加小心以确保注释正确。返回类型应与self的类型或cls的实例类型匹配。这可以使用TypeVar来完成这些变量会跟踪实际传递给self和cls的内容#dogs.pyfrom datetime import datefrom typing import Type, TypeVarTAnimal TypeVar(TAnimal, boundAnimal)class Animal:def __init__(self, name: str, birthday: date) - None:self.name nameself.birthday birthdayclassmethoddef newborn(cls: Type[TAnimal], name: str) - TAnimal:return cls(name, date.today())def twin(self: TAnimal, name: str) - TAnimal:cls self.__class__return cls(name, self.birthday)class Dog(Animal):def bark(self) - None:print(f{self.name} says woof!)fido Dog.newborn(Fido)pluto fido.twin(Pluto)fido.bark()pluto.bark()在这个例子中有几个需要注意的点类型变量TAnimal用于表示返回值可能是Animal的子类的实例。.我们指定Animal是TAnimal的上限。指定绑定意味着TAnimal将是Animal子类之一。这可以正确限制所允许的类型。typing.Type []是type()的类型。需要注意是cls的类方法需要使用这种形式注解而self就不用使用。注解 *args 和 **kwargs在面向对象的游戏版本中我们添加了在命令行上命名玩家的选项。这是通过在程序名称后面列出玩家名称来完成的$python game.py GeirArne Dan JoannaDan: ♢A Joanna: ♡9 P1: ♣A GeirArne: ♣2Dan: ♡A Joanna: ♡6 P1: ♠4 GeirArne: ♢8Dan: ♢K Joanna: ♢Q P1: ♣K GeirArne: ♠5Dan: ♡2 Joanna: ♡J P1: ♠7 GeirArne: ♡KDan: ♢10 Joanna: ♣3 P1: ♢4 GeirArne: ♠8Dan: ♣6 Joanna: ♡Q P1: ♣Q GeirArne: ♢JDan: ♢2 Joanna: ♡4 P1: ♣8 GeirArne: ♡7Dan: ♡10 Joanna: ♢3 P1: ♡3 GeirArne: ♠2Dan: ♠K Joanna: ♣5 P1: ♣7 GeirArne: ♠JDan: ♠6 Joanna: ♢9 P1: ♣J GeirArne: ♣10Dan: ♠3 Joanna: ♡5 P1: ♣9 GeirArne: ♠QDan: ♠A Joanna: ♠9 P1: ♠10 GeirArne: ♡8Dan: ♢6 Joanna: ♢5 P1: ♢7 GeirArne: ♣4关于类型注释:即使名称是字符串元组也应该只注释每个名称的类型。换句话说您应该使用字符串而不是元组[字符串]就像下面这个例子:class Game:def __init__(self, *names: str) - None:Set up the deck and deal cards to 4 playersdeck  Deck.create(shuffleTrue)self.names  (list(names)  P1 P2 P3 P4.split())[:4]self.hands  {n: Player(n, h) for n, h in zip(self.names, deck.deal(4))}类似地如果有一个接受**kwargs的函数或方法那么你应该只注释每个可能的关键字参数的类型。Callables可调用类型函数是Python中的一类对象。可以使用函数作为其他函数的参数。这意味着需要能够添加表示函数的类型提示。函数以及lambdas、方法和类都由type的Callable对象表示。参数的类型和返回值通常也表示。例如Callable[[A1, A2, A3] Rt]表示一个函数它有三个参数分别具有A1、A2和A3类型。函数的返回类型是Rt。在下面这个例子, 函数 do_twice() 传入一个Callable类型的func参数并指明传入的函数的参数类型为str返回值类型为str。比如传入参数create_greeting.#do_twice.pyfrom typing import Callabledef do_twice(func: Callable[[str], str], argument: str) - None:print(func(argument))print(func(argument))def create_greeting(name: str) - str:return fHello {name}do_twice(create_greeting, Jekyll)Example: Hearts让我们以红心游戏的完整例子来结束。您可能已经从其他计算机模拟中了解了这个游戏。下面是对规则的简要回顾:四名玩家每人玩13张牌。持有♣2的玩家开始第一轮必须出♣2。如果可能的话玩家轮流打牌跟随领头的一套牌。在第一套牌中打出最高牌的玩家赢了这个把戏并在下一个回合中成为开始牌的玩家。玩家不能用♡除非♡已经在之前的技巧中玩过。玩完所有牌后玩家如果拿到某些牌就会获得积分♠Q为13分每个♡1为分一场比赛持续几轮直到得到100分以上。得分最少的玩家获胜具体游戏规则可以网上搜索一下.在这个示例中没有多少新的类型概念是尚未见过的。因此我们将不详细讨论这段代码而是将其作为带注释代码的示例。#hearts.pyfrom collections import Counterimport randomimport sysfrom typing import Any, Dict, List, Optional, Sequence, Tuple, Unionfrom typing import overloadclass Card:SUITS ♠ ♡ ♢ ♣.split()RANKS 2 3 4 5 6 7 8 9 10 J Q K A.split()def __init__(self, suit: str, rank: str) - None:self.suit suitself.rank rankpropertydef value(self) - int:The value of a card is rank as a numberreturn self.RANKS.index(self.rank)propertydef points(self) - int:Points this card is worthif self.suit ♠ and self.rank Q:return 13if self.suit ♡:return 1return 0def __eq__(self, other: Any) - Any:return self.suit other.suit and self.rank other.rankdef __lt__(self, other: Any) - Any:return self.value other.valuedef __repr__(self) - str:return f{self.suit}{self.rank}class Deck(Sequence[Card]):def __init__(self, cards: List[Card]) - None:self.cards cardsclassmethoddef create(cls, shuffle: bool False) - Deck:Create a new deck of 52 cardscards [Card(s, r) for r in Card.RANKS for s in Card.SUITS]if shuffle:random.shuffle(cards)return cls(cards)def play(self, card: Card) - None:Play one card by removing it from the deckself.cards.remove(card)def deal(self, num_hands: int) - Tuple[Deck, ...]:Deal the cards in the deck into a number of handsreturn tuple(self[i::num_hands] for i in range(num_hands))def add_cards(self, cards: List[Card]) - None:Add a list of cards to the deckself.cards cardsdef __len__(self) - int:return len(self.cards)overloaddef __getitem__(self, key: int) - Card: ...overloaddef __getitem__(self, key: slice) - Deck: ...def __getitem__(self, key: Union[int, slice]) - Union[Card, Deck]:if isinstance(key, int):return self.cards[key]elif isinstance(key, slice):cls self.__class__return cls(self.cards[key])else:raise TypeError(Indices must be integers or slices)def __repr__(self) - str:return .join(repr(c) for c in self.cards)class Player:def __init__(self, name: str, hand: Optional[Deck] None) - None:self.name nameself.hand Deck([]) if hand is None else handdef playable_cards(self, played: List[Card], hearts_broken: bool) - Deck:List which cards in hand are playable this roundif Card(♣, 2) in self.hand:return Deck([Card(♣, 2)])lead played[0].suit if played else Noneplayable Deck([c for c in self.hand if c.suit lead]) or self.handif lead is None and not hearts_broken:playable Deck([c for c in playable if c.suit ! ♡])return playable or Deck(self.hand.cards)def non_winning_cards(self, played: List[Card], playable: Deck) - Deck:List playable cards that are guaranteed to not win the trickif not played:return Deck([])lead played[0].suitbest_card max(c for c in played if c.suit lead)return Deck([c for c in playable if c best_card or c.suit ! lead])def play_card(self, played: List[Card], hearts_broken: bool) - Card:Play a card from a cpu players handplayable self.playable_cards(played, hearts_broken)non_winning self.non_winning_cards(played, playable)# Strategyif non_winning:# Highest card not winning the trick, prefer pointscard max(non_winning, keylambda c: (c.points, c.value))elif len(played) 3:# Lowest card maybe winning, avoid pointscard min(playable, keylambda c: (c.points, c.value))else:# Highest card guaranteed winning, avoid pointscard max(playable, keylambda c: (-c.points, c.value))self.hand.cards.remove(card)print(f{self.name} - {card})return carddef has_card(self, card: Card) - bool:return card in self.handdef __repr__(self) - str:return f{self.__class__.__name__}({self.name!r}, {self.hand})class HumanPlayer(Player):def play_card(self, played: List[Card], hearts_broken: bool) - Card:Play a card from a human players handplayable sorted(self.playable_cards(played, hearts_broken))p_str .join(f{n}: {c} for n, c in enumerate(playable))np_str .join(repr(c) for c in self.hand if c not in playable)print(f {p_str} (Rest: {np_str}))while True:try:card_num int(input(f {self.name}, choose card: ))card playable[card_num]except (ValueError, IndexError):passelse:breakself.hand.play(card)print(f{self.name} {card})return cardclass HeartsGame:def __init__(self, *names: str) - None:self.names (list(names) P1 P2 P3 P4.split())[:4]self.players [Player(n) for n in self.names[1:]]self.players.append(HumanPlayer(self.names[0]))def play(self) - None:Play a game of Hearts until one player go bustscore Counter({n: 0 for n in self.names})while all(s 100 for s in score.values()):print(\nStarting new round:)round_score self.play_round()score.update(Counter(round_score))print(Scores:)for name, total_score in score.most_common(4):print(f{name:15} {round_score[name]:3} {total_score:3})winners [n for n in self.names if score[n] min(score.values())]print(f\n{ and .join(winners)} won the game)def play_round(self) - Dict[str, int]:Play a round of the Hearts card gamedeck Deck.create(shuffleTrue)for player, hand in zip(self.players, deck.deal(4)):player.hand.add_cards(hand.cards)start_player next(p for p in self.players if p.has_card(Card(♣, 2)))tricks {p.name: Deck([]) for p in self.players}hearts False# Play cards from each players hand until emptywhile start_player.hand:played: List[Card] []turn_order self.player_order(startstart_player)for player in turn_order:card player.play_card(played, hearts_brokenhearts)played.append(card)start_player self.trick_winner(played, turn_order)tricks[start_player.name].add_cards(played)print(f{start_player.name} wins the trick\n)hearts hearts or any(c.suit ♡ for c in played)return self.count_points(tricks)def player_order(self, start: Optional[Player] None) - List[Player]:Rotate player order so that start goes firstif start is None:start random.choice(self.players)start_idx self.players.index(start)return self.players[start_idx:] self.players[:start_idx]staticmethoddef trick_winner(trick: List[Card], players: List[Player]) - Player:lead trick[0].suitvalid [(c.value, p) for c, p in zip(trick, players) if c.suit lead]return max(valid)[1]staticmethoddef count_points(tricks: Dict[str, Deck]) - Dict[str, int]:return {n: sum(c.points for c in cards) for n, cards in tricks.items()}if __name__ __main__:# Read player names from the command lineplayer_names sys.argv[1:]game HeartsGame(*player_names)game.play()对于上面的代码有几个注意点:对于难以使用Union或类型变量表达的类型关系比如魔法函数可以使用overload装饰器。子类对应于子类型因此可以在任何需要玩家的地方使用HumanPlayer。当子类从超类重新实现方法时类型注释必须匹配。有关示例请参阅HumanPlayer.play_card()。开始游戏时你控制第一个玩家。输入数字以选择要玩的牌。下面是一个游戏的例子突出显示的线条显示了玩家的选择:$python hearts.py GeirArne Aldren Joanna BradStarting new round:Brad - ♣20: ♣5 1: ♣Q 2: ♣K (Rest: ♢6 ♡10 ♡6 ♠J ♡3 ♡9 ♢10 ♠7 ♠K ♠4)GeirArne, choose card: 2GeirArne ♣KAldren - ♣10Joanna - ♣9GeirArne wins the trick0: ♠4 1: ♣5 2: ♢6 3: ♠7 4: ♢10 5: ♠J 6: ♣Q 7: ♠K (Rest: ♡10 ♡6 ♡3 ♡9)GeirArne, choose card: 0GeirArne ♠4Aldren - ♠5Joanna - ♠3Brad - ♠2Aldren wins the trick...Joanna - ♡JBrad - ♡20: ♡6 1: ♡9 (Rest: )GeirArne, choose card: 1GeirArne ♡9Aldren - ♡AAldren wins the trickAldren - ♣AJoanna - ♡QBrad - ♣J0: ♡6 (Rest: )GeirArne, choose card: 0GeirArne ♡6Aldren wins the trickScores:Brad 14 14Aldren 10 10GeirArne 1 1Joanna 1 1当前目前所有的typing方法的使用场景就结束了。觉得有用的朋友可以点个已看或者转发到朋友圈分享更更多好友。
http://www.pierceye.com/news/813962/

相关文章:

  • 上传网站空间天津专门做网站的公司
  • 无锡企业做网站大庆油田内网主页网址
  • 网站开发合同 下载山西正规网站建设报价公司
  • seo好的外贸网站怎么用wordpress建立本地网站
  • 网站备案号查询有名vi设计公司
  • 呼市做网站建设的公司哪家好易班班级网站建设展示PPT
  • 网站制作精品案例欣赏中国建设局网站首页
  • 几分钟做网站网站网格布局
  • 为企业做网站策划案网站建设 爱诚科技公司
  • 怎么把网站放到阿里云做网站外链需要多少钱
  • 南宁市网站建设公司给人做网站网站
  • 怎么查网站的备案号网站页面设计需求
  • 手机网站怎么做的好网页设计参考书籍
  • 网站建设和网络推广方案网站建设技术可行性分析
  • 免费建站网站自助建站的网站建站淘宝网站开始怎么做的
  • 旅游电网站建设目标公司注册成本
  • 建设婚恋网站基本功能有哪些毕业设计网页
  • 广州贸易网站杭州关键词推广优化方案
  • 怎么注册自己的网站wordpress静态设置
  • 网站收录有什么好处仿糗事百科wordpress
  • 面试网站建设工程师鞍山市城市建设管理局网站
  • 电商网站建设与管理柳州网站建设多少钱
  • 网站的访问量统计怎么做企业网站建设基本标准
  • 网站开发服务属于什么行业先做网站再备案吗
  • 做零售去哪个外贸网站专业做网站较好的公司
  • 网站运营职业分析2233网页游戏大全
  • 深圳网站制作880网站建设课设总结
  • 瑶海区网站建设公司中铁建设集团有限公司基础设施事业部
  • wordpress 用js网站备案 seo
  • 网站一级域名和二级域名区别自己怎么做外贸英文网站