概述
最近即将开始一个比较大的项目,对程序性能和持久运行的要求会比较高,所以这里研究了一下最新的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!