Python:以 MyPy 接受的方式提示 CTypes“指向 X 的指针”类型

问题描述 投票:0回答:3

我在 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 的指针”的有效方法是什么?

python ctypes type-hinting mypy
3个回答
1
投票

我认为你可以使用类似的东西:

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

1
投票

根据我在这里找到的内容,我找到了一个不完美(但足够好)的解决方案: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]]
兼容。对于这些情况,我使用了类型联合;不太理想,但它完成了工作。

我暂时不回答这个问题,以防有一天有人能找到更好的解决方案。


0
投票

ctypes 早于 Python 类型注释。因此:

的地址
意义 c类型 C++ 等价物
获取
foo
pfoo = pointer(foo)
Foo* pfoo = &foo
指向-
Foo
的指针(一种类型)
PFoo = POINTER(Foo)
using PFoo = Foo*

因此,即使 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
© www.soinside.com 2019 - 2024. All rights reserved.