qiwihui / blog

技术和思考,基于issues
https://qiwihui.com
43 stars 2 forks source link

Python 函数变量类型注释会导致用 Cython 编译后执行与直接执行结果不一致 #117

Open qiwihui opened 3 years ago

qiwihui commented 3 years ago

最近工作中遇到关于函数类型注释引起的错误,特此记录一下。

起因是公司的项目为了安全和执行速度,在发布时会使用 Cython 转为 C 语言并编译成动态连接库进行调用,但是有个函数在 Python 执行时正常,但是在动态连接库中却执行错误。

错误复现

测试用例 test.py

def ip_str(ips: str):
    ips = [x for x in ips]

def ip(ips):
    ips = [x for x in ips]

编译 compile.py

from setuptools import setup
from Cython.Build import cythonize

extensions = ["test.py",]

setup(name='test',
      ext_modules=cythonize(extensions)
)

运行编译:

python compile.py build

编译之后进入对应 so 文件目录 build/lib-{平台架构},运行对比:

>>> import test
>>> test.ip("123")
>>> test.ip_str("123")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 5, in test.ip_str
TypeError: Expected str, got list

经过调查发现,当函数变量做了类型注释时,不能重新赋值为其他类型,否则会在 Cython 编译后执行时报错。

Cython 可以在编译时推断出部分简单的错误,比如:

def ip1():
    ips: str = '1'
    ips = ['1']
Error compiling Cython file:
------------------------------------------------------------
...

def ip1():
    ips: str = '1'
    ips = ['1']
         ^
------------------------------------------------------------

test.py:10:10: Cannot coerce list to type 'str object'

但如果代码比较复杂,则只能在运行时才会出错。所以上述错误只能在执行的时候才被抛出。

原因

Cython 将 Python 转为 C 代码比较后类型注释与否代码比较:

主要区别在于,类型注释增加了变量检测 __Pyx_ArgTypeTest ,以及之后赋值 ips 时的类型检测 PyString_CheckExact;没有变量类型注释则进行了变量推测,判断是否为List( PyList_CheckExact)或者Tuple ( PyTuple_CheckExact),还是可迭代类型。

结论

  1. 类型注释在编译后会简化处理流程;
  2. 类型注释的变量不能赋值为其他类型。

GitHub repo: qiwihui/blog

Follow me: @qiwihui

Site: QIWIHUI