VSCode MyPy error: "ioctl" has incompatible type "my_struct"; expected "Union[int, str]"

VSCode MyPy error: "ioctl" has incompatible type "my_struct"; expected "Union[int, str]"

我有以下 python 片段生成 MyPy“问题”(在 vscode 中)。

my_struct = MyStruct()    
#! set mutable flag to true to place data in our object.
fcntl.ioctl( dev_hand.fileno(), my_ioctl_id, my_struct, True )

错误是:

“ioctl”的参数 3 具有不兼容的类型“my_struct”;预期“联合 [int, str]”

MyStruct 是一个 ctypes 结构。使用 ioctl() 和 ctypes 结构的所有示例都显示将实例传递给 ioctl()。这确实有效,除了现在 MyPy 正在抱怨。

我不想转换为字节并使用 struct 模块手动 pack/unpack(我认为这是一种解决方案)。

我在 Linux (Debian Buster) 上使用 Python 3.7.3mypy 0.782

谢谢布伦丹。


注意:我忘了说我的代码是针对 Python 2.7 的,因为它是 Debian Jessie 目标系统的遗留物。我正在为 mypy 使用 --py2 开关(在 Python 3 上必须 运行)。

ioctl() 函数具有以下签名,似乎来自 vscode 服务器(远程 ssh)ms-python .... typeshed/stdlib/3/fcntl。 pyi`

def ioctl(fd: _AnyFile,
          request: int,
          arg: Union[int, bytes] = ...,
          mutate_flag: bool = ...) -> Any: ...

这是一个更完整的代码示例。

from typing import ( BinaryIO, )

import ioctl
import fcntl

from ctypes import ( c_uint32, Structure, addressof )

class Point ( Structure ) :
    _fields_ = [ ( 'x', c_uint32 ), ( 'y', c_uint32 ) ]

def ioctl_get_point (
        dev_hand,
        ) :
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy does NOT complain at all **

def ioctl_get_point_2 (
        dev_hand,               # type: BinaryIO
        ) :
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_3 (
        dev_hand,
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_4 (
        dev_hand,               # type: BinaryIO
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_5 (
        dev_hand,               # type: BinaryIO
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, addressof( point ), True )   #! ** MyPy does NOT complain at all **
    return point

对我来说,使用@CristiFati 建议的 ctypes.addressof() 函数似乎是最简单的解决方案。

不幸的是,这不起作用。 ioctl() 函数需要知道对象的大小。

谢谢布伦丹。

mypy 在此处遵循 fnctl.ioctl 函数的规范:

The parameter arg can be one of an integer, an object supporting the read-only buffer interface (like bytes) or an object supporting the read-write buffer interface (like bytearray).

因此投诉是合法的。

I'd prefer not to convert to bytes and manually pack/unpack with the struct module

TYPE_CHECKING 常量的帮助下,您可以为 fnctl.ioctl 引入一个带有类型提示的本地存根,它将覆盖 stdlib 的类型提示:

import ctypes
from typing import TYPE_CHECKING


class MyStruct(ctypes.Structure):
    _fields_ = [...]


if TYPE_CHECKING:  # this is only processed by mypy
    from typing import Protocol, Union, TypeVar

    class HasFileno(Protocol):
        def fileno(self) -> int: ...

    FileDescriptorLike = Union[int, HasFileno]

    _S = TypeVar('_S', bound=ctypes.Structure)

    def ioctl(__fd: FileDescriptorLike, __request: int, __arg: Union[int, bytes, _S] = ..., __mutate_flag: bool = ...) -> int: ...

else:  # this will be executed at runtime and ignored by mypy
    from fcntl import ioctl


my_struct = MyStruct(...)
my_ioctl_id = ...
dev_hand = ...

ioctl(dev_hand.fileno(), my_ioctl_id, my_struct, True)  # mypy won't complain here anymore

首先,该错误消息看起来像 Python 2 mypy 错误消息,而不是 Python 3 mypy 错误消息。 Python 3 的 stubsfcntl.ioctl 声明与该错误消息不匹配。 (你仍然会收到 Python 3 mypy 的错误消息,但它会是不同的消息。)

其次,fcntl.ioctl 接受任何支持缓冲区接口的对象(包括您的结构),但 mypy 甚至不知道缓冲区接口是什么。支持buffer接口的对象没有annotation,也没有办法静态识别支持buffer接口的对象。目前 不可能 正确注释像 fcntl.ioctl 这样的函数。关于此问题有 open issues,但看不到解决方案。

您最好的选择可能是在该行上添加 # type: ignore 评论。