我在 C 库上绑定了一个大型 Python,具有复杂的内存管理。为了帮助解决这个问题,我为字符串制定了以下别名(这里是我的定义和后续问题的最小可重现示例)。
from typing import Type, TypeAlias
from ctypes import POINTER, pointer, c_char, c_char_p
#normal python string
p_useable_p_str = str
#the result of a my_str.decode("utf-8")
c_useable_p_str = bytes
#used for string literals returned by the C, which should not be freed
p_useable_c_str = c_char_p
#used for allocated strings returned by the C, which need to be freed later
c_useable_c_str = POINTER(c_char) #the problematic line
def example(hello: c_useable_c_str): # source of the MyPy error
pass
当遵循上述定义+使用约定时,我的代码运行良好(内存正确释放,必要时一致继承上述内容等)。
POINTER(c_char)
在代码的其余部分中具有预期的行为。
但是,用 MyPy 分析上述内容,我得到:
playground.py:10: error: Variable "playground.c_useable_c_str" is not valid as a type
playground.py:10: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)
对于使用
c_useable_c_str
别名的任何内容,我都会收到此错误。当然,我阅读了上面链接的文档中的部分,并尝试以多种不同的方式使用 Type 和 TypeAlias - 但没有成功。
唯一让 MyPy 满意的语法是
c_useable_c_str = pointer[c_char]
但是,当实际使用类型别名的定义运行代码时,我收到以下错误(MyPy 没有看到,所以我怀疑 MyPy 末端存在错误,或者缺少标准输入):
Traceback (most recent call last):
File "/home/fulguritude/ProfessionalWork/LEDR/Orchestra-AvesTerra/Python_binding/playground2.py", line 92, in <module>
c_useable_c_str = pointer[c_char]
TypeError: 'builtin_function_or_method' object is not subscriptable
关于我应该如何使事情保持一致有什么想法吗?
TLDR:使用 MyPy 和 CType 键入提示和别名、“指向 X 的指针”的有效方法是什么?
我认为你可以使用类似的东西:
if TYPE_CHECKING: # valid for mypy
c_useable_c_str = pointer[c_char] # the problematic line
else: # valid at run time
c_useable_c_str = pointer
根据我在这里找到的内容,我找到了一个不完美(但足够好)的解决方案:https://github.com/python/mypy/issues/7540(
thijsmie
评论,2021年5月21日评论) .
if not TYPE_CHECKING:
# Monkeypatch typed pointer from typeshed into ctypes
# NB: files that wish to import ctypes.pointer should all import `pointer` from this file instead
class pointer_fix:
@classmethod
def __class_getitem__(cls, item):
return POINTER(item)
pointer = pointer_fix
它确实解决了 MyPy 对
c_useable_c_str
的抱怨,并且当提供的参数不是兼容类型时,使用 c_useable_c_str
的函数仍然会出现正确的键入错误,所以这很好。
好吧,除了以下情况:
class t_string(c_useable_c_str):
pass
class t_string_p(pointer[t_string]):
pass
MyPy 似乎无法理解
t_string_p
与类型 pointer[pointer[c_char]]
兼容。对于这些情况,我使用了类型联合;不太理想,但它完成了工作。
我暂时不回答这个问题,以防有一天有人能找到更好的解决方案。
ctypes 早于 Python 类型注释。因此:
意义 | c类型 | C++ 等价物 |
---|---|---|
获取
|
|
|
指向- 的指针(一种类型) |
|
|
因此,即使 mypy “接受”
pointer[foo]
,它也没有意义,因为 pointer
返回 pointers,而不是 types。
相反,我们希望
POINTER(Foo)
和 POINTER[Foo]
表示相同的意思。我们可以使用 typing.GenericAlias
来帮助解决这个问题:
# Python 3.10+
import ctypes
import typing
class POINTER[T](ctypes._Pointer):
class _GenericAlias(typing.GenericAlias):
def __repr__(self):
val = super().__repr__()
ibra = val.find('[')
idot = val.rfind('.', 0, ibra)
return f"{val[:idot+1]}POINTER{val[ibra:]}"
def __class_getitem__(cls, *args):
ptrtype = ctypes.POINTER(*args)
alias = POINTER._GenericAlias(ptrtype, *args)
return alias
对于较旧的 Python:
# Python 3.9 (maybe 3.8 too)
import ctypes
import typing
import typing_extensions
T = typing.TypeVar('T')
class POINTER(typing.Generic[T], ctypes._Pointer):
class _GenericAlias(typing_extensions.GenericAlias):
def __repr__(self):
val = super().__repr__()
ibra = val.find('[')
idot = val.rfind('.', 0, ibra)
return f"{val[:idot+1]}POINTER{val[ibra:]}"
def __class_getitem__(cls, *args):
ptrtype = ctypes.POINTER(*args)
alias = POINTER._GenericAlias(ptrtype, *args)
return alias