[EuroPython笔记] Yoichi Takai: 在python 3.10中使用静态类

EuroPython今天又放出了几十个视频,于是我就挑了几个看。其中一个吸引我的就是关于在python 3.10中使用静态类的。演讲者是从日本连线过去的。欧洲人能大老远请他,肯定有两把刷子。

首先,我安装了python 3.10,今天去python的网站一看,今天正好3.10发布,怎么这么巧。我不下一个都不好意思了。

https://www.python.org/

安装完以后,我就跟着视频写代码了。

我的conda base是python 3.8,所以不用装了。我又装了一个python 3.6

conda create -n py36 python=3.6

这样,我可以在python 3.6, 3.8, 3.10 之间切换。

为什么使用静态类

python静态类已经有5年了。从2015的python 3.5就开始支持静态类了。

  • 代码使用者能知道类型
  • 他会报错
  • 当你在函数的返回值后面按点的时候,“自动完成”功能将可以使用

比如,我们可以比较以下示例

def without_typing(s):
    return "Hello " + s

without_typing(1)
def with_typing(s: str) -> str:
    return "Hello " + s

with_typing(1)

第一个示例没有type,第二个有。

当你直接运行他们的时候,他们都会报错

TypeError: can only concatenate str (not “int”) to str

但是如果没有类型,那么你只能在程序运行的时候,才知道错了。

而第二种写法,在编辑的时候,就会报错了。

这里,我试了vscode和pycharm。

vscode默认不报错,需要安装mypy,之后才会有错误提示。

VSCode安装mypy

按下F1,输入“Python: Select Linter”,然后选mypy

vscode的错误信息是:

Argument 1 to “with_typing” has incompatible type “int”; expected "str"mypy(error)

pycharm的错误信息是:

Expected type ‘str’, got ‘int’ instead

代码review的时候

def need_new_post():
    if ...: return None
    elif ...: return False
    else ...: return post_id # this is a string

如果没有类型限制,那么你的返回值可能有各种类型。这给这个函数的使用者造成了很大的困扰。

使用built-in类型

有5中built-in类型,可以直接使用,他们是:

bool, bytes, float, int, str

当然,你也可以直接使用None,比如,函数的返回值,可以是None。

你也可以用Any表示任意类型,

from typing import Any

python 3.9 里的泛型集合

集合有dict, frozenset, list, set, tuple

3.9以后他们可以在[]里面写上具体类型。

3.7和3.8, 需要引用

from __future__ import annotations

3.6,需要使用

from typing import List, Dict, Set, Tuple

假如,我们要打印一个str list里面的所有字符串,在python 3.6, 3.8, 3.10的代码分别是:

Python 3.6:

from typing import List

def print_all(l: List[str]) -> None:
    for i in l:
        print(i)

print_all(['a', 'b', 'c'])

Python 3.8:

from __future__ import annotations

def print_all(l: list[str]) -> None:
    for i in l:
        print(i)

print_all(['a', 'b', 'c'])

Python 3.10:

def print_all(l: list[str]) -> None:
    for i in l:
        print(i)

print_all(['a', 'b', 'c'])

不得不说,python还真麻烦。。。

其他的集合可以从collections库引用,如

from collections import deque, defaultdict

和原型有关的可以从collectoins.abc引用。

from collections.abc import Callable, Iterator

按照Yoichi 的说法,typing库在3.9以后就过期了(Deprecated),但是,我发现,无论是vscode,还是pycharm,都没有任何提示。所以,我觉得他说这里过期了,应该不准确。因为你如果说一个东西过期了,那么,应该有警告或者报错才对。否则,从技术角度,我们不能说他过期了。

Union

从3.10开始,Union可以直接用竖线分割了

def func(a: int | str | float) -> None:
    pass

而3.10之前,必须用typing

from typing import Union
Union[int|str|float]

Option

Option[T] 相当于 Union[T, None],Option类的放在非Option类的后面。

Callable

callable表示一个函数对象。

用户定义的泛型

现在python也支持用户自定义的泛型了。

from typing import TypeVar, Generic

KT, VT = TypeVar('KT'), TypeVar('VT')
class Mapping(Generic[KT, VT]):
    def __init__(self, d: dict):
        self.__dict__ = d

    def __getitem__(self, key: KT) -> VT:
        return self.__dict__[key]

Parameter Specification Variables

Parameter specification是另一个python 3.10新特性。PSV其实就是表示了函数的所有参数。

下面的代码写了一个wait函数,他可以在任何一个函数的前面,增加一个x: int参数,在函数执行的时候,会睡眠x秒。

我们可以在一个爬虫函数上面,加上这个,这样使得这个函数可以休息x秒,避免爬的太快被封IP。

from time import sleep
from typing import Callable, ParamSpec, TypeVar, Concatenate

P = ParamSpec("P")
R = TypeVar("R")


def wait(f: Callable[P, R]) -> Callable[Concatenate[int, P], R]:
    def inner(x: int, *args: P.args, **kargs: P.kwargs) -> R:
        sleep(x)
        return f(*args, **kargs)
    return inner

def do_something():
    print("something is done")

do_something_5 = wait(do_something)

do_something_5(5)

TypeAlias

首先,TypeAlias要解决一个相互引用的问题。

(当然,我个人绝对最好避免出现相互引用)

比如,你定义了鸡和蛋,你是先有鸡,还是现有蛋呢?

如果你还是用类型安全的方式去做,却没有TypeAlias,就会报错。如下面的代码

class Chicken:
    def __init__(self, name, eggs: list[Egg]):
        self.eggs = eggs

class Egg:
    def __init__(self, c: Chicken) -> None:
        self.Chicken = c

报错

NameError: name ‘Egg’ is not defined

这时候,我们就需要使用TypeAlias。

在3.10之前,我们可以在list[Egg]外面,加上引号,变成字符串,告诉编译器,我会在以后定义这个类。

class Chicken:
    def __init__(self, name: str, eggs: 'list[Egg]'):
        self.name = name
        self.eggs = eggs
        print(f"I have {
       len(eggs)} eggs.")

class Egg:
    def __init__(self, c: Chicken) -> None:
        self.Chicken = c
        print(f"My mom is {
       c.name}")

c = Chicken('hero', [])
e = Egg(c)

在3.10,更好的方法是使用TypeAlias


from typing import TypeAlias


EggList: TypeAlias = 'list[Egg]'

class Chicken:
    def __init__(self, name: str, eggs: EggList):
        self.name = name
        self.eggs = eggs
        print(f"I have {
       len(eggs)} eggs.")

class Egg:
    def __init__(self, c: Chicken) -> None:
        self.Chicken = c
        print(f"My mom is {
       c.name}")

c = Chicken('hero', [])
e = Egg(c)

TypeGuards

TypeGuards其实就是类型检查,他的实际返回值是bool型的。

这样写,是为了给编译器看的。

他只检查第一个参数。当第一个参数是self,或者cls的时候,检查第二个参数。

from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    """Determine whether all objects in the list are strings"""
    return all(isinstance(x, str) for x in val)

a = ['a', 'b', 'c']
print(is_str_list(a))

b = ['a', 'b', 1]
print(is_str_list(b))

引用

https://ep2021.europython.eu/talks/ASCmqFk-getting-started-with-statically-typed-programming-in-python-310/

PDF:
https://files.speakerdeck.com/presentations/e920d72ac9b44c67a10918223f579e53/euro.pdf

你可能感兴趣的:(python,vscode,pycharm)