mypy快速入门

概述

最近即将开始一个比较大的项目,对程序性能和持久运行的要求会比较高,所以这里研究了一下最新的python各个编译器及优化方式。其中,pypy和mypyc两种是比较合适的优化方式。
pypy最大的问题是不能使用cpython的大量第三方库,这是致命的问题,还好云端是django开发,这个支持。但是可以预想,之后开发可能会遇到一些问题。
另外一个mypyc编译器,这个东西是建立在cpython上的,通过静态标注的方式将代码进行标注,然后在运行之前可以将其编译为二进制代码,提升运行速度。

速度对比

这是一个初步的比较,单纯比较运行速度。

import time
from typing import Tuple

#cpython:13.5
#pypy:7.8
#mypy:10.5
def fb(x:int,y:int)->Tuple[int,int]:
    return y,x+y

def test()->float:
    x:int=0
    y:int=1
    t:float=time.time()
    for i in range(1000000):
        x,y=fb(x,y)
    return time.time()-t

print(test())

mypy优化的地方就是通过静态方法和静态类替代动态类,达到速度提升4倍的效果。(这里测试实际上是不合适的)综合效果略显失望,不过没得选了。。。

环境搭建

ubuntu:
1、安装:
pip3 install mypy
2、添加环境变量
~/.local/bin将这个添加到环境变量里面去
3、测试mypyc,将上面我的代码写在一个文件里面(比如fbnq.py),运行mypyc fbnq.py,会得到so文件,然后新建一个文件,直接import fbnq,如果能正常运行即ok。
4、可能会出现异常,提示缺少文件,那这个时候你可以在github里面下载整个完整的mypy包,在对应文件位置里面将文件粘贴过去,我就遇到一个缺少rt…名字的包,我直接复制粘贴到指定位置的,就没问题了。
5、pycharm加入mypy提示,pycharm的菜单-setting里面,有一项:plugin里面,搜索mypy,然后可以安装这个插件。插件安装完之后,需要在插件里面指定mypy的位置,位置就在~/.local/bin/mypy(如果之前没有安装错误的话)
之后,环境就搭建好了。
提示:要优化运行速度,通过mypyc进行优化,大概指令就像使用python一样的。

与cpython区别

核心的工作其实就一个:怎么把里面的各种类和各种方法(函数)进行标记,尽量不要在同一个变量里面一会用int一会用str一会又是list和dict之类的。
1、由于这里是使用静态类和方法,所以之前动态添加属性的方式可能会报错,举个例子

class A:
    a:int
b=A()
b.b:int=1 #error:禁止动态添加属性

我觉得这个无伤大雅,可以提前在__init__里面进行初始化,也有益于养成良好的编程习惯。(如果非要使用,需要自己初始化__getattr__和__setattr__)
2、无法使用元类,应该是mateclass这一块的东西,,,说实话这东西用着很危险,容易超出开发人员的控制,平时大家也用不到,如果要用到元类,就没法用mypy和pypy了。
3、无法热修复,就是mokey patch,这个我无法评估,没用过。
基本上如果开发人员有良好的习惯且没有特殊需求,影响不大。毕竟能提升4倍速度呢。。

将现有代码转为标记类型的代码

官方提供了两种运行时进行标记的方法,可以在运行程序的途中,自动进行代码标记,当然效果无法评估,我也美用过。。
比如:MonkeyType

标记方法

入门请参考上面的环境搭建的时候的代码

变量的标记

从最简单的开始,变量标记主要就是在第一次使用变量的时候进行标记,这里python以3.6为例子(3.6增加了标记的pep484)

# 基本变量的标记
age: int = 1
# 如果不想给初始值,可以这样
age:int

child: bool
if age < 18:
    child = True
else:
    child = False
#内置类型的标记
#typing是一个用于标记的类库,里面有很多需要的标注用的类
from typing import List, Set, Dict, Tuple, Optional
x: int = 1
x: float = 1.0
x: bool = True
x: str = "test"
x: bytes = b"test"

#标注列表和集合,这里列表就不能混搭了,当然如果你进行混搭也没关系,进行混搭则不会对混搭的进行静态编译优化,使用Any标注
x: List[int] = [1]
x: Set[int] = {6, 7}
x: Dict[str, float] = {'field': 2.0}
# 元组,这里我不太喜欢,因为我写方法喜欢返回元组,这里限制比较大,
x: Tuple[int, str, float] = (3, "yes", 7.5)
x:Tuple[int,...]=(1,2,3,4)
# Optional[str]可以同时使值为str或空,
x: Optional[str] = some_function()
if x is not None:
    print(x.upper())
#多用断言,可以让你将混搭没有优化的代码过渡到优化代码去。
assert x is not None
print(x.upper())

函数的标记

from typing import Callable, Iterator, Union, Optional, List

# 没啥好说的
def stringify(num: int) -> str:
    return str(num)

def plus(num1: int, num2: int) -> int:
    return num1 + num2
# 带默认值
def f(num1: int, my_float: float = 3.5) -> float:
    return num1 + my_float

# 标注回调函数,说实话这个功能很有用,我一度以为回调函数不需要标注。。
x: Callable[[int, float], float] = f

# 返回可迭代对象
def g(n: int) -> Iterator[int]:
    i = 0
    while i < n:
        yield i
        i += 1

def send_email(address: Union[str, List[str]],
               sender: str,
               cc: Optional[List[str]],
               bcc: Optional[List[str]],
               subject='',
               body: Optional[List[str]] = None
               ) -> bool:
    ...

# 参数用两个下划线来命名的时候,没法自己引用,说实话没干过,这里其实类似于python的似有变量,都是假的。。。
def quux(__x: int) -> None:
    pass

quux(3)  # Fine
quux(__x=3)  # Error

一些需要关注的东西

可迭代对象是一个很重要的东西和概念,在之后应该会有专门针对可迭代对象的讲解(不知道自己能不能足够勤奋写下来。。。)

from typing import Union, Any, List, Optional, cast

#要找出mypy在程序中任何地方为表达式推断的类型,请将其包装在reveal_type()中。mypy将打印类型为的错误消息;在运行代码之前再次删除它。
reveal_type(1)  # -> Revealed type is 'builtins.int'

# 如果一个变量可能是多种类型,用Union进行标注
x: List[Union[int, str]] = [3, 5, "test", "fun"]

# Any用于标注不知道的类型
x: Any = mystery_function()

# 如果用空容器或“无”初始化变量,可能需要通过提供类型注释来帮助MyPy。
x: List[str] = []
x: Optional[str] = None

# 这使得每个位置参数和每个关键字参数都成为“str”
def call(self, *args: str, **kwargs: str) -> str:
    request = make_request(*args, **kwargs)
    return self.do_api_query(request)

# 如果引用其他包的时候,那些包没有注释,可以这么标注来提示mypy忽略错误。
x = confusing_function()  # type: ignore  # https://github.com/python/mypy/issues/1167

# “CAST”是帮助您重写表达式的推断类型的辅助函数。只有MyPy——没有运行时检查。
a = [4]
b = cast(List[int], a)  # Passes fine
c = cast(List[str], a)  # Passes fine (no runtime check)
reveal_type(c)  # -> Revealed type is 'builtins.list[builtins.str]'
print(c)  # -> [4]; the object is not cast

# If you want dynamic attributes on your class, have it override "__setattr__"
# or "__getattr__" in a stub or in your source code.
#如果您想要类的动态属性,请让它覆盖存根或源代码中的“__setattr__”或“__getattr__”。
#说实话这块还没测试过。,。。。
# "__setattr__" allows for dynamic assignment to names
# "__getattr__" allows for dynamic access to names
class A:
    # 这将允许分配给任何A.x,如果X是与“值”相同的类型(使用“值:任意”以允许任意类型)
    def __setattr__(self, name: str, value: int) -> None: ...

    def __getattr__(self, name: str) -> int: ...

a.foo = 42  # Works
a.bar = 'Ex-parrot'  # Fails type checking

鸭子类型

在典型的Python代码中,许多可以将列表或dict作为参数的函数只需要将其参数设为“类似于列表”或“类似于dict”即可。“类似列表”或“类似字典”(或类似“东西”)的特定含义称为“鸭子类型”,并且标准化了在惯用Python中常见的几种鸭子类型。

from typing import Mapping, MutableMapping, Sequence, Iterable, List, Set

#可迭代对象,支持使用for进行迭代的对象,需要支持"len"和"__getitem__"
def f(ints: Iterable[int]) -> List[str]:
    return [str(x) for x in ints]
f(range(1, 3))

# Mapping描述了一个我们将不会改变类-字典类(有 __getitem__), MutableMapping描述的我们可能会改变的类-字典类(有 __setitem__)
def f(my_dict: Mapping[int, str]) -> List[int]:
    return list(my_dict.keys())

f({3: 'yes', 4: 'no'})

def f(my_mapping: MutableMapping[int, str]) -> Set[str]:
    my_mapping[5] = 'maybe'
    return set(my_mapping.values())

f({3: 'yes', 4: 'no'})

类的标注

class MyClass:
    # 原文这里为实例变量,这里触及到一个我的知识盲区,,,,会在另外一篇文章讲解。
    attr: int
    charge_percent: int = 100
    # 类变量的标注方法:
    class_attr:ClassVar[int]
   
    def __init__(self) -> None:
        ...

    # self不用标记
    def my_method(self, num: int, str1: str) -> str:
        return num * str1

# 这里就比较烦,还要这么写
x: MyClass = MyClass()

# 类变量的标注方法。。。
class Car:
    seats: ClassVar[int] = 4
    passengers: ClassVar[List[str]]

# 您也可以在“初始化”中声明属性的类型
class Box:
    def __init__(self) -> None:
        self.items: List[str] = []

协同和异步

暂时我用不到,,,以后在说。。

杂项

import sys
import re
from typing import Match, AnyStr, IO

# "typing.Match" 描述了正则表达式的内容
x: Match[str] = re.match(r'[0-9]+', "15")

# Use IO[] for functions that should accept or return any
# object that comes from an open() call (IO[] does not
# distinguish between reading, writing or other modes)
# 修改默认输入输出源的时候需要注意的地方,当然这个一般在ACM或其他竞赛可能经常用。
def get_sys_IO(mode: str = 'w') -> IO[str]:
    if mode == 'w':
        return sys.stdout
    elif mode == 'r':
        return sys.stdin
    else:
        return sys.stdout

# 注意代码顺序,
def f(foo: A) -> int:  # This will fail
    ...

class A:
    ...

# 正确示例1
def f(foo: 'A') -> int:  # Ok
    ...
# 正确示例2
#将两个调整一下顺序

其他

想要更好的使用该工具,还是要多看开发文档,
另外,good luck!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值