首页 南方天气预报正文

刘华强,运用类型注解让 Python 代码更易读-雷火电竞csgo

admin 南方天气预报 2019-11-21 196 0

咱们知道 Python 是一种动态言语,在声明一个变量时咱们不需求显式地声明它的类型,例如下面的比方:

a = 2
print('1 + a =', 1 + a)

运转成果:

1 + a = 3

这儿咱们首要声明晰一个变量 a,并将其赋值为了 2,然后将最终的成果打印出来,程序输出来了正确的成果。但在这个过程中,咱们没有声明它究竟是什么类型。

但假如这时分咱们将 a 变成一个字符串类型,成果会是怎样的呢?改写如下:

a = '2'
print('1 + a =', 1 + a)

运转成果:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

直接报错了,过错原因是咱们进行了字符串类型的变量和数值类型变量的加和,两种数据类型不同,是无法进行相加的。

假如咱们将上面的句子改写成一个办法界说:

def add(a):
return a + 1

这儿界说了一个办法,传入一个参数,然后将其加 1 并回来。

假如这时分假如用下面的办法调用,传入的参数是一个数值类型:

add(2)

则能够正常输出成果 3。但假如咱们传入的参数并不是咱们希望的类型,比方传入一个字符类型,那么就会相同报方才相似的过错。

但又因为 Python 的特性,许多状况下咱们并不用去声明它的类型,因而从办法界说上面来看,咱们实践上是不知道一个办法的参数究竟应该传入什么类型的。

这样其实就造成了许多不方便的当地,在某些状况下一些杂乱的办法,假如不凭借于一些额定的阐明,咱们是不知道参数究竟是什么类型的。

因而,Python 中的类型注解就显得比较重要了。

类型注解

在 Python 3.5 中,Python PEP 484 引进了类型注解(type hints),在 Python 3.6 中,PEP 526 又进一步引进了变量注解(Variable Annotations),所以上面的代码咱们改写成如下写法:

a: int = 2
print('5 + a =', 5 + a)
def add(a: int) -> int:
return a + 1

具体的语法是能够概括为两点:

  • 在声明变量时,变量的后边能够加一个冒号,后边再写上变量的类型,如 int、list 等等。
  • 在声明办法回来值的时分,能够在办法的后边加一个箭头,后边加上回来值的类型,如 int、list 等等。

在 PEP 8 中,具体的格局是这样规则的:

  • 在声明变量类型时,变量后方紧跟一个冒号,冒号后边跟一个空格,再跟上变量的类型。
  • 在声明办法回来值的时分,箭头左面是办法界说,箭头右边是回来值的类型,箭头左右两头都要留有空格。

有了这样的声明,今后咱们假如看到这个办法的界说,咱们就知道传入的参数类型了,如调用 add 办法的时分,咱们就知道传入的需求是一个数值类型的变量,而不是字符串类型,十分直观。

但值得注意的是,这种类型和变量注解实践上只是一种类型提示,对运转实践上是没有影响的,比方调用 add 办法的时分,咱们传入的不是 int 类型,而是一个 float 类型,它也不会报错,也不会对参数进行类型转化,如:

add(1.5)

咱们传入的是一个 float 类型的数值 1.5,看下运转成果:

2.5

能够看到,运转成果正常输出,并且 1.5 并没有经过强制类型转化变成 1,不然成果会变成 2。

因而,类型和变量注解只是供给了一种提示,关于运转实践上没有任何影响。

不过有了类型注解,一些 IDE 是能够辨认出来并提示的,比方 PyCharm 就能够辨认出来在调用某个办法的时分参数类型不一致,会提示 WARNING。

比方上面的调用,假如在 PyCharm 中,就会有如下提示内容:

Expected type 'int', got 'float' instead
This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings or in Python 3 function annotations.

别的也有一些库是支撑类型查看的,比方 mypy,装置之后,运用 mypy 即可查看出 Python 脚本中不符合类型注解的调用状况。

上面只是用一个简略的 int 类型做了实例,下面咱们再看下一些相对杂乱的数据结构,例如列表、元组、字典等类型怎样样来声明。

可想而知了,列表用 list 表明,元组用 tuple 表明,字典用 dict 来表明,那么很自然地,在声明的时分咱们就很自然地写成这样了:

names: list = ['Germey', 'Guido']
version: tuple = (3, 7, 4)
operations: dict = {'show': False, 'sort': True}

这么看上去没有问题,的确声明为了对应的类型,但实践上并不能反映整个列表、元组的结构,比方咱们只经过类型注解是不知道 names 里边的元素是什么类型的,只知道 names 是一个列表 list 类型,实践上里边都是字符串 str 类型。咱们也不知道 version 这个元组的每一个元素是什么类型的,实践上是 int 类型。但这些信息咱们都无从得知。因而说,只是凭仗 list、tuple 这样的声明是十分“弱”的,咱们需求一种更强的类型声明。

这时分咱们就需求凭借于 typing 模块了,它供给了十分“强“的类型支撑,比方 List[str]、Tuple[int, int, int] 则能够表明由 str 类型的元素组成的列表和由 int 类型的元素组成的长度为 3 的元组。所以上文的声明写法能够改写成下面的姿态:

from typing import List, Tuple, Dict
names: List[str] = ['Germey', 'Guido']
version: Tuple[int, int, int] = (3, 7, 4)
operations: Dict[str, bool] = {'show': False, 'sort': True}

这样一来,变量的类型便能够十分直观地体现出来了。

现在 typing 模块也现已被加入到 Python 规范库中,不需求装置第三方模块,咱们就能够直接运用了。

typing

下面咱们再来具体看下 typing 模块的具体用法,这儿首要会介绍一些常用的注解类型,如 List、Tuple、Dict、Sequence 等等,了解了每个类型的具体运用办法,咱们能够称心如意的对任何变量进行声明晰。

在引进的时分就直接经过 typing 模块引进就好了,例如:

from typing import List, Tuple

List

List、列表,是 list 的泛型,根本等同于 list,这以后紧跟一个方括号,里边代表了构成这个列表的元素类型,如由数字构成的列表能够声明为:

var: List[int or float] = [2, 3.5]

别的还能够嵌套声明都是能够的:

var: List[List[int]] = [[1, 2], [2, 3]]

Tuple、NamedTuple

Tuple、元组,是 tuple 的泛型,这以后紧跟一个方括号,方括号中依照次序声明晰构成本元组的元素类型,如 Tuple[X, Y] 代表了构成元组的第一个元素是 X 类型,第二个元素是 Y 类型。

比方想声明一个元组,别离代表名字、年纪、身高,三个数据类型别离为 str、int、float,那么能够这么声明:

person: Tuple[str, int, float] = ('Mike', 22, 1.75)

相同地也能够运用类型嵌套。

NamedTuple,是 collections.namedtuple 的泛型,实践上就和 namedtuple 用法彻底一致,但个人其实并不引荐运用 NamedTuple,引荐运用 attrs 这个库来声明一些具有表征意义的类。

Dict、Mapping、MutableMapping

Dict、字典,是 dict 的泛型;Mapping,映射,是 collections.abc.Mapping 的泛型。依据官方文档,Dict 引荐用于注解回来类型,Mapping 引荐用于注解参数。它们的运用办法都是相同的,这以后跟一个中括号,中括号内别离声明键名、键值的类型,如:

def size(rect: Mapping[str, int]) -> Dict[str, int]:
return {'width': rect['width'] + 100, 'height': rect['width'] + 100}

这儿将 Dict 用作了回来值类型注解,将 Mapping 用作了参数类型注解。

MutableMapping 则是 Mapping 目标的子类,在许多库中也常常用 MutableMapping 来替代 Mapping。

Set、AbstractSet

Set、调集,是 set 的泛型;AbstractSet、是 collections.abc.Set 的泛型。依据官方文档,Set 引荐用于注解回来类型,AbstractSet 用于注解参数。它们的运用办法都是相同的,这以后跟一个中括号,里边声明调集中元素的类型,如:

def describe(s: AbstractSet[int]) -> Set[int]:
return set(s)

这儿将 Set 用作了回来值类型注解,将 AbstractSet 用作了参数类型注解。

Sequence

Sequence,是 collections.abc.Sequence 的泛型,在某些状况下,咱们或许并不需求严厉区别一个变量或参数究竟是列表 list 类型仍是元组 tuple 类型,咱们能够运用一个更为泛化的类型,叫做 Sequence,其用法相似于 List,如:

def square(elements: Sequence[float]) -> List[float]:
return [x ** 2 for x in elements]

NoReturn

NoReturn,当一个办法没有回来成果时,为了注解它的回来类型,咱们能够将其注解为 NoReturn,例如:

def hello() -> NoReturn:
print('hello')

Any

Any,是一种特别的类型,它能够代表一切类型,静态类型查看器的一切类型都与 Any 类型兼容,一切的无参数类型注解和回来类型注解的都会默许运用 Any 类型,也就是说,下面两个办法的声明是彻底等价的:

def add(a):
return a + 1
def add(a: Any) -> Any:
return a + 1

原理相似于 object,一切的类型都是 object 的子类。但假如咱们将参数声明为 object 类型,静态参数类型查看便会抛出过错,而 Any 则不会,具体能够参阅官方文档的阐明:https://docs.python.org/zh-cn/3/library/typing.html?highlight=typing#the-any-type。

TypeVar

TypeVar,咱们能够凭借它来自界说兼容特定类型的变量,比方有的变量声明为 int、float、None 都是符合要求的,实践就是代表恣意的数字或许空内容都能够,其他的类型则不能够,比方列表 list、字典 dict 等等,像这样的状况,咱们能够运用 TypeVar 来表明。

例如一个人的身高,便能够运用 int 或 float 或 None 来表明,但不能用 dict 来表明,所以能够这么声明:

height = 1.75
Height = TypeVar('Height', int, float, None)
def get_height() -> Height:
return height

这儿咱们运用 TypeVar 声明晰一个 Height 类型,然后将其用于注解办法的回来成果。

NewType

NewType,咱们能够凭借于它来声明一些具有特别意义的类型,例如像 Tuple 的比方相同,咱们需求将它表明为 Person,即一个人的意义,但但从外表上声明为 Tuple 并不直观,所以咱们能够运用 NewType 为其声明一个类型,如:

Person = NewType('Person', Tuple[str, int, float])
person = Person(('Mike', 22, 1.75))

这儿实践上 person 就是一个 tuple 类型,咱们能够对其像 tuple 相同正常操作。

Callable

Callable,可调用类型,它一般用来注解一个办法,比方咱们方才声明晰一个 add 办法,它就是一个 Callable 类型:

print(Callable, type(add), isinstance(add, Callable))

运转成果:

typing.Callable  True

在这儿尽管二者 add 运用 type 办法得到的成果是 function,但实践上运用 isinstance 办法判别的确是 True。

Callable 在声明的时分需求运用 Callable[[Arg1Type, Arg2Type, ...], ReturnType] 这样的类型注解,将参数类型和回来值类型都要注解出来,例如:

def date(year: int, month: int, day: int) -> str:
return f'{year}-{month}-{day}'
def get_date_fn() -> Callable[[int, int, int], str]:
return date

这儿首要声明晰一个办法 date,接纳三个 int 参数,回来一个 str 成果,get_date_fn 办法回来了这个办法本身,它的回来值类型就能够标记为 Callable,中括号内别离标记了回来的办法的参数类型和回来值类型。

Union

Union,联合类型,Union[X, Y] 代表要么是 X 类型,要么是 Y 类型。

联合类型的联合类型等价于展平后的类型:

Union[Union[int, str], float] == Union[int, str, float]

仅有一个参数的联合类型会坍缩成参数本身,比方:

Union[int] == int

剩余的参数会被越过,比方:

Union[int, str, int] == Union[int, str]

在比较联合类型的时分,参数次序会被疏忽,比方:

Union[int, str] == Union[str, int]

这个在一些办法参数声明的时分比较有用,比方一个办法,要么传一个字符串表明的办法名,要么直接把办法传过来:

def process(fn: Union[str, Callable]):
if isinstance(fn, str):
# str2fn and process
pass
elif isinstance(fn, Callable):
fn()

这样的声明在一些类库办法界说的时分十分常见。

Optional

Optional,意思是说这个参数能够为空或现已声明的类型,即 Optional[X] 等价于 Union[X, None]。

但值得注意的是,这个并不等价于可选参数,当它作为参数类型注解的时分,不代表这个参数能够不传递了,而是说这个参数能够传为 None。

如当一个办法履行成果,假如履行结束就不回来过错信息, 假如发作问题就回来过错信息,则能够这么声明:

def judge(result: bool) -> Optional[str]:
if result: return 'Error Occurred'

Generator

假如想代表一个生成器类型,能够运用 Generator,它的声明比较特别,这以后的中括号紧跟着三个参数,别离代表 YieldType、SendType、ReturnType,如:

def echo_round() -> Generator[int, float, str]:
sent = yield 0
while sent >= 0:
sent = yield round(sent)
return 'Done'

在这儿 yield 关键字后边紧跟的变量的类型就是 YieldType,yield 回来的成果的类型就是 SendType,最终生成器 return 的内容就是 ReturnType。

当然许多状况下,生成器往往只需求 yield 内容就够了,咱们是不需求 SendType 和 ReturnType 的,能够将其设置为空,如:

def infinite_stream(start: int) -> Generator[int, None, None]:
while True:
yield start
start += 1

事例实战

接下来让咱们看一个实践的项目,看看常常用到的类型一般是怎样运用的。

这儿咱们看的库是 requests-html,是由 Kenneth Reitz 所开发的,其 GitHub 地址为:https://github.com/psf/requests-html,下面咱们首要看看它的源代码中一些类型是怎么声明的。

这个库的源代码其实就一个文件,那就是 https://github.com/psf/requests-html/blob/master/requests_html.py,咱们看一下它里边的一些 typing 的界说和办法界说。

首要 Typing 的界说部分如下:

from typing import Set, Union, List, MutableMapping, Optional
_Find = Union[List['Element'], 'Element']
_XPath = Union[List[str], List['Element'], str, 'Element']
_Result = Union[List['Result'], 'Result']
_HTML = Union[str, bytes]
_BaseHTML = str
_UserAgent = str
_DefaultEncoding = str
_URL = str
_RawHTML = bytes
_Encoding = str
_LXML = HtmlElement
_Text = str
_Search = Result
_Containing = Union[str, List[str]]
_Links = Set[str]
_Attrs = MutableMapping
_Next = Union['HTML', List[str]]
_NextSymbol = List[str]

这儿能够看到首要用到的类型有 Set、Union、List、MutableMapping、Optional,这些在上文都现已做了解说,别的这儿运用了屡次 Union 来声明晰一些新的类型,如 _Find 则要么是是 Element 目标的列表,要么是单个 Element 目标,_Result 则要么是 Result 目标的列表,要么是单个 Result 目标。别的 _Attrs 其实就是字典类型,这儿用 MutableMapping 来表明晰,没有用 Dict,也没有用 Mapping。

接下来再看一个 Element 类的声明:

class Element(BaseParser):
"""An element of HTML.
:param element: The element from which to base the parsing upon.
:param url: The URL from which the HTML originated, used for ``absolute_links``.
:param default_encoding: Which encoding to default to.
"""
__slots__ = [
'element', 'url', 'skip_anchors', 'default_encoding', '_encoding',
'_html', '_lxml', '_pq', '_attrs', 'session'
]
def __init__(self, *, element, url: _URL, default_encoding: _DefaultEncoding = None) -> None:
super(Element, self).__init__(element=element, url=url, default_encoding=default_encoding)
self.element = element
self.tag = element.tag
self.lineno = element.sourceline
self._attrs = None
def __repr__(self) -> str:
attrs = ['{}={}'.format(attr, repr(self.attrs[attr])) for attr in self.attrs]
return "".format(repr(self.element.tag), ' '.join(attrs))
@property
def attrs(self) -> _Attrs:
"""Returns a dictionary of the attributes of the :class:`Element `
(`learn more `_).
"""
if self._attrs is None:
self._attrs = {k: v for k, v in self.element.items()}
# Split class and rel up, as there are ussually many of them:
for attr in ['class', 'rel']:
if attr in self._attrs:
self._attrs[attr] = tuple(self._attrs[attr].split())
return self._attrs

这儿 __init__ 办法接纳十分多的参数,一起运用 _URL 、_DefaultEncoding 进行了参数类型注解,别的 attrs 办法运用了 _Attrs 进行了回来成果类型注解。

全体看下来,每个参数的类型、回来值都进行了明晰地注解,代码可读性大大提高。

以上就是类型注解和 typing 模块的具体介绍。

本节代码能够在大众号「进击的Coder」回复「类型注解」获取。

崔庆才

静觅博客博主,《Python3网络爬虫开发实战》作者

个人大众号:进击的Coder

雷火电竞版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

system,简讯:11月19日河北省46蛋白花生粕报价坚持平稳-雷火电竞csgo

  • 张承中,窗口不说NO 打造金牌服务-雷火电竞csgo

    张承中,窗口不说NO 打造金牌服务-雷火电竞csgo

  • 脸过敏发红痒怎么办,上海华通铂银买卖市场买卖行情11月19日(10:00)(银钯铑钌铱铼)-雷火电竞csgo

    脸过敏发红痒怎么办,上海华通铂银买卖市场买卖行情11月19日(10:00)(银钯铑钌铱铼)-雷火电竞csgo

  • 支气管扩张,央企华能集团改主见:控股协鑫新能源变为收买财物-雷火电竞csgo

    支气管扩张,央企华能集团改主见:控股协鑫新能源变为收买财物-雷火电竞csgo

  • 分手合约,南大光电(300346)融资融券信息(11-15)-雷火电竞csgo

    分手合约,南大光电(300346)融资融券信息(11-15)-雷火电竞csgo

  • 显卡,我武生物(300357)融资融券信息(11-15)-雷火电竞csgo

    显卡,我武生物(300357)融资融券信息(11-15)-雷火电竞csgo

  • 锁阳,光一科技(300356)融资融券信息(11-15)-雷火电竞csgo

    锁阳,光一科技(300356)融资融券信息(11-15)-雷火电竞csgo

  • cam,什么是“杀猪盘”圈套?这10类都是-雷火电竞csgo

    cam,什么是“杀猪盘”圈套?这10类都是-雷火电竞csgo

  • 最近发表

      雷火电竞csgo_雷火电竞2_雷火竞猜

      http://www.myriaresearch.com/

      |

      Powered By

      使用手机软件扫描微信二维码

      关注我们可获取更多热点资讯

      雷火电竞出品