现在能在网上找到很多很多的学习资源,有免费的也有收费的,当我拿到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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**