最全python类型检测最终指南--Typing的使用_typing_extensions(2),2024年最新面试复盘怎么复盘

现在能在网上找到很多很多的学习资源,有免费的也有收费的,当我拿到1套比较全的学习资源之前,我并没着急去看第1节,我而是去审视这套资源是否值得学习,有时候也会去问一些学长的意见,如果可以之后,我会对这套学习资源做1个学习计划,我的学习计划主要包括规划图和学习进度表。

分享给大家这份我薅到的免费视频资料,质量还不错,大家可以跟着学习

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

annotations{‘nothing’: <class ‘str’>}


类型注解


 


如上所述,注释是在Python 3中引入的,并且它们没有被反向移植到Python 2.这意味着如果您正在编写需要支持旧版Python的代码,则无法使用注释。


要向函数添加类型注释,您可以执行以下操作:​​​​​​​



import math
def circumference(radius): # type: (float) -> float return 2 * math.pi * radius


类型注释只是注释,所以它们可以用在任何版本的Python中。


类型注释由类型检查器直接处理,所以不存在`__annotations__`字典对象中:​​​​​​​



circumference.annotations{}


类型注释必须以type: 字面量开头,并与函数定义位于同一行或下一行。如果您想用几个参数来注释一个函数,您可以用逗号分隔每个类型:​​​​​​​



def headline(text, width=80, fill_char=“-”): # type: (str, int, str) -> str return f" {text.title()} ".center(width, fill_char)
print(headline(“type comments work”, width=40))


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



headlines.py   def headline(      text,           # type: str      width=80,       # type: int      fill_char=“-”,  # type: str  ):                  # type: (…) -> str      return f" {text.title()} ".center(width, fill_char)  print(headline(“type comments work”, width=40))


通过Python和Mypy运行示例:​​​​​​​



$ python headlines.py---------- Type Comments Work ----------
$ mypy headline.py$


 


`如果传入一个字符串width="full",再次运行mypy会出现一下错误。` ​​​​​​​



$ mypy headline.pyheadline.py:10: error: Argument “width” to “headline” has incompatible type “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


 


### 到目前为止,您只在类型提示中使用了str,float和bool等基本类型。但是Python类型系统非常强大,它可以支持多种更复杂的类型。


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


* 序列和映射的类型,如元组,列表和字典
* 键入别名,使代码更容易阅读
* 该函数和方法不返回任何内容
* 可以是任何类型的对象


在简要介绍了一些类型理论之后,您将看到更多用Python指定类型的方法。您可以在这里找到代码示例:https://github.com/realpython/materials/tree/master/python-type-checking


#### Example: A Deck of Cards


以下示例显示了一副常规纸牌的实现



# game.py   import random   SUITS = “♠ ♡ ♢ ♣”.split()  RANKS = “2 3 4 5 6 7 8 9 10 J Q K A”.split()   def create_deck(shuffle=False):      “”“Create a new deck of 52 cards”“”     deck = [(s, r) for r in RANKS for s in SUITS]     if shuffle:         random.shuffle(deck)     return deck
 def deal_hands(deck):     “”“Deal the cards in the deck into four hands”“”     return (deck[0::4], deck[1::4], deck[2::4], deck[3::4])
 def play():     “”“Play a 4-player card game”“”     deck = create_deck(shuffle=True)     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,简直类型bool`


`typing` 还包括其他的很多类型比如 `Counter`, `Dequ``e`, `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 cards”“”     deck = [(s, r) for r in RANKS for s in SUITS]     if shuffle:        random.shuffle(deck)     return deck


 


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


注意: 元组和列表的声明是有区别的


元组是不可变序列,通常由固定数量的可能不同类型的元素组成。例如,我们将卡片表示为套装和等级的元组。通常,您为n元组编写元组[t\_1,t\_2,...,t\_n]。


 


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


 


在许多情况下,你的函数会期望某种顺序,并不关心它是列表还是元组。在这些情况下,您应该使用typing.Sequence在注释函数参数时:​​​​​​​



from typing import List, Sequence
def square(elems: Sequence[float]) -> List[float]: return [x**2 for x in elems]


 


使用 `Sequence` 是一个典型的鸭子类型的例子. 也就意味着`可以使用``len()` 和 `.__getitem__()等方法。`


#### 类型别名


使用嵌套类型(如卡片组)时,类型提示可能会变得非常麻烦。你可能需要仔细看List [Tuple [str,str]],才能确定它与我们的一副牌是否相符.


现在考虑如何注释`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 hands”“”     return (deck[0::4], deck[1::4], deck[2::4], deck[3::4])


这也太麻烦了!


不怕,我们还可以使用起别名的方式把注解的类型赋值给一个新的变量,方便在后面使用,就像下面这样:​​​​​​​



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


 


现在我们就可以使用别名对之前的代码进行注解了:​​​​​​​



def deal_hands(deck: Deck) -> Tuple[Deck, Deck, Deck, Deck]:     “”“Deal the cards in the deck into four hands”“”     return (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.py   def 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 NoReturn
def black_hole() -> NoReturn: raise Exception(“There is no going back …”)


因为black\_hole( )总是引发异常,所以它永远不会正确返回。


#### Example: Play Some Cards


让我们回到我们的纸牌游戏示例。在游戏的第二个版本中,我们像以前一样向每个玩家发放一张牌。然后选择一个开始玩家并且玩家轮流玩他们的牌。虽然游戏中没有任何规则,所以玩家只会玩随机牌:


​​​​​​​



# game.py   import random  from typing import List, Tuple   SUITS = “♠ ♡ ♢ ♣”.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 cards”“”     deck = [(s, r) for r in RANKS for s in SUITS]     if shuffle:         random.shuffle(deck)     return deck
def deal_hands(deck: Deck) -> Tuple[Deck, Deck, Deck, Deck]:     “”“Deal the cards in the deck into four hands”“”     return (deck[0::4], deck[1::4], deck[2::4], deck[3::4])
 def choose(items):     “”“Choose and return a random item”“”     return random.choice(items)
 def player_order(names, start=None):     “”“Rotate player order so that start goes first”“”     if 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 game”“”     deck = create_deck(shuffle=True)     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, start=start_player)
     # Randomly play cards from each player’s hand until empty     while hands[start_player]:         for name in turn_order:             card = choose(hands[name])             hands[name].remove(card)             print(f"{name}: {card[0] + card[1]:❤️}  “, 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` Type


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



import randomfrom typing import Any, Sequence
def choose(items: Sequence[Any]) -> Any: return random.choice(items)


 


这或多或少意味着它:items是一个可以包含任何类型的项目的序列,而choose()将返回任何类型的这样的项目。不是很严谨,此时请考虑以下示例:​​​​​​​



# choose.py   import random  from typing import Any, Sequence   def 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 2​​​​​​​



import randomfrom typing import Any, Sequence
def choose(items: Sequence[Any]) -> Any: return random.choice(items)


使用Any的问题在于您不必要地丢失类型信息。您知道如果将一个字符串列表传递给choose(),它将返回一个字符串。


类型变量


类型变量是一个特殊变量,可以采用任何类型,具体取决于具体情况。


 


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



# choose.py   import random  from typing import Sequence, TypeVar   Choosable = 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.py   from choose import choose   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])


前两个例子应该有类型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.py   import random  from typing import Sequence, TypeVar   Choosable = 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 “choose” cannot be “object”


 


还要注意,在第二个例子中,即使输入列表只包含int对象,该类型也被认为是float类型的。这是因为Choosable仅限于str和float,int是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 Sized
def len(obj: Sized) -> int: return obj.len()


 


除此之外,在Typing中还包括以下模块 `Container`, `Iterable`, `Awaitable`, 还有 `ContextManager`.


你也可以声明自定的协议, 通过导入typing\_extensions模块中的Protocol协议对象,然后写一个继承该方法的子类,像下面这样:​​​​​​​



from typing_extensions import Protocol
class 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, start=None):     “”“Rotate player order so that start goes first”“”     if 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, Optional
def player_order( names: Sequence[str], start: Optional[str] = None) -> Sequence[str]: …


等价于`Union类型的` `Union[None, str],意思是这个参数的值类型为str,默认的话可以是`


请注意,使用Optional或Union时,必须注意变量是否在后面有操作。比如上面的例子通过判断start是否为None。如果不判断None的情况,在做静态类型检查的时候会发生错误:



1 # player_order.py
2
3 from typing import Sequence, Optional
4
5 def player_order(
6 names: Sequence[str], start: Optional[str] = None
7 ) -> 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 incompatible type “Optional[str]”; expected “str”


也可以使用以下操作,声明参数start的类型。​​​​​​​



def player_order(names: Sequence[str], start: str = None) -> Sequence[str]: …


如果你不想 Mypy 出现报错,你可以使用命令



–no-implicit-optional


 


Example: The Object(ive) of the Game


接下来我们会重写上面的扑克牌游戏,让它看起来更面向对象,以及适当的使用注解。


将我们的纸牌游戏翻译成以下几个类, `Card`, `Deck`, `Player`, `Game` ,下面是代码实现。​​​​​​​



game.py  import random import sys   class 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 = suit        self.rank = rank      def repr(self):         return f"{self.suit}{self.rank}"  class Deck:     def init(self, cards):         self.cards = cards      @classmethod     def create(cls, shuffle=False):         “”“Create a new deck of 52 cards”“”         cards = [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 hands”“”         cls = 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 = name         self.hand = hand      def play_card(self):         “”“Play a card from the player’s hand”“”         card = random.choice(self.hand.cards)         self.hand.cards.remove(card)         print(f"{self.name}: {card!r:❤️}  “, end=”“)         return card  class Game:     def init(self, *names):         “”“Set up the deck and deal cards to 4 players””"         deck = Deck.create(shuffle=True)         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 game”“”         start_player = random.choice(self.names)         turn_order = self.player_order(start=start_player)          # Play cards from each player’s hand until empty         while self.hands[start_player].hand.cards:             for name in turn_order:                 self.hands[name].play_card()             print()      def player_order(self, start=None):         “”“Rotate player order so that start goes first”“”         if 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 line     player_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 = suit         self.rank = rank      def repr(self) -> str:         return f"{self.suit}{self.rank}"


`__init__()` 的返回值总是为None


Class作为类型


#### 类别和类型之间有对应关系。例如,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:    @classmethod    def create(cls, shuffle: bool = False) -> “Deck”:         “”“Create a new deck of 52 cards”“”         cards = [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 = name         self.hand = hand


`通常,注释不会在运行时使用。这为推迟对注释的评估提供了动力。该提议不是将注释评估为Python表达式并存储其值,而是存储注释的字符串表示形式,并仅在需要时对其进行评估。`


 


`这种功能计划在Python 4.0中成为标准。但是,在Python 3.7及更高版本中,可以通过导入__future__属性的annotations来实现:`​​​​​​​



from future import annotations
class Deck: @classmethod def create(cls, shuffle: bool = False) -> Deck: …


 


使用 `__future__之后就可以使用Deck对象替换字符串"Deck"了。`


返回 self 或者 cls


#### 如前所述,通常不应该注释self或cls参数。在一定程度上,这是不必要的,因为self指向类的实例,所以它将具有类的类型。在Card示例中,self拥有隐式类型Card。此外,显式地添加这种类型会很麻烦,因为还没有定义该类。所以需要使用字符串“Card”声明返回类型。


但是,有一种情况可能需要注释self或cls。考虑如果你有一个其他类继承的超类,并且有返回self或cls的方法会发生什么:​​​​​​​



dogs.py from datetime import date class Animal: def init(self, name: str, birthday: date) -> None: self.name = name self.birthday = birthday @classmethod def 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 "bark"dogs.py:25: error: “Animal” has no attribute “bark”


问题是,即使继承的Dog.newborn()和Dog.twin()方法将返回一个Dog,注释表明它们返回一个Animal。


 


在这种情况下,您需要更加小心以确保注释正确。返回类型应与self的类型或cls的实例类型匹配。这可以使用TypeVar来完成,这些变量会跟踪实际传递给self和cls的内容:​​​​​​​



dogs.py

from datetime import datefrom typing import Type, TypeVar
TAnimal = TypeVar(“TAnimal”, bound=“Animal”)
class Animal: def init(self, name: str, birthday: date) -> None: self.name = name self.birthday = birthday
@classmethod def 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 players”“”         deck = Deck.create(shuffle=True)         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.py    from typing import Callable    def do_twice(func: Callable[[str], str], argument: str) -> None:      print(func(argument))      print(func(argument))   def create_greeting(name: str) -> str:     return f"Hello {name}"   do_twice(create_greeting, “Jekyll”)


 



### 最后

Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

#### 👉Python所有方向的学习路线👈

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

![](https://img-blog.csdnimg.cn/img_convert/604bae65027d4d67fb62410deb210454.png)

#### 👉Python必备开发工具👈

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

![](https://img-blog.csdnimg.cn/img_convert/fa276175617e0048f79437bd30465479.png)



#### 👉Python全套学习视频👈

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

![](https://img-blog.csdnimg.cn/img_convert/16ac689cb023166b2ffa9c677ac40fc0.png)



#### 👉实战案例👈



学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。



因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。

![](https://img-blog.csdnimg.cn/img_convert/0d8c31c50236a205928a1d8ae8a0b883.png)



#### 👉大厂面试真题👈

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

![](https://img-blog.csdnimg.cn/img_convert/99461e47e58e503d2bc1dc6f4668534a.png)

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618317507)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值