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!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章