Ошибка VSCode MyPy: ioctl имеет несовместимый тип my_struct; ожидаемый союз [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 )

Ошибка:

Аргумент 3 для ioctl имеет несовместимый тип my_struct; ожидаемый союз [int, str]

MyStruct - это структура ctypes. Все примеры использования ioctl() со структурами ctypes показывают передачу экземпляра в ioctl(). Действительно, это работает, но теперь MyPy жалуется.

Я бы предпочел не преобразовывать в байты и вручную упаковывать / распаковывать с помощью модуля struct (который, как я полагаю, является одним из решений).

Я использую Python 3.7.3 в Linux (Debian Buster) с mypy 0.782

Спасибо, Брендан.


ПРИМЕЧАНИЕ. Я забыл упомянуть, что мой код нацелен на Python 2.7, поскольку он унаследован от целевой системы Debian Jessie. Я использую переключатель --py2 для mypy (который должен работать на 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

Мне кажется, что использование функции ctypes.addressof(), предложенной @CristiFati, является самым простым решением.

К сожалению, это не работает. Функция ioctl() должна знать размер объекта.

Спасибо, Брендан.


person BrendanSimon    schedule 31.07.2020    source источник
comment
Вы уверены, что используете Python 3.7.3 и используете версию mypy для Python 3? Похоже на сообщение об ошибке Python 2. (Mypy Python 3 по-прежнему будет выдавать вам сообщение об ошибке, но это будет другое сообщение об ошибке.)   -  person user2357112 supports Monica    schedule 31.07.2020
comment
да. Я использую mypy в режиме python2 (--py2). Я обновил вопрос, добавив дополнительную информацию. Я думаю, что ответ CristiFati об использовании ctypes.addressof() - самое простое решение.   -  person BrendanSimon    schedule 03.08.2020


Ответы (2)


mypy следует спецификациям функции fnctl.ioctl здесь:

Параметр arg может быть целым числом, объектом, поддерживающим интерфейс буфера только для чтения (например, bytes), или объектом, поддерживающим интерфейс буфера чтения-записи (например, bytearray).

Таким образом, жалоба является законной.

Я бы предпочел не преобразовывать в байты и вручную упаковывать / распаковывать с помощью модуля struct

С помощью константы 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
person hoefling    schedule 31.07.2020
comment
Однако структуры ctypes действительно поддерживают интерфейс буфера, а строки - нет! Электронная документация противоречит строке документации и подписи, воспринимаемой mypy, и фактическое рассмотрение аргумента кажется смешанным. - person user2357112 supports Monica; 31.07.2020
comment
Спасибо. Хотя, похоже, много работы. Я думаю, что ответ CristiFati об использовании ctypes.addressof () является самым простым решением. - person BrendanSimon; 03.08.2020
comment
так что ctypes.addressof() не будет работать. Имеет смысл, поскольку ioctl() необходимо знать размер объекта (и требуется буферное пространство для изменяемого возвращаемого значения). Хорошо, я снова посмотрел на ваше решение, и теперь оно имеет для меня больше смысла. Я предполагаю, что могу использовать это один раз, и он будет работать для всех подклассов структуры. Собираюсь дать этому шанс. - person BrendanSimon; 03.08.2020
comment
Я могу подтвердить, что это работает, и буду моим принятым ответом. Большое спасибо !! - person BrendanSimon; 03.08.2020

Во-первых, это сообщение об ошибке выглядит как сообщение об ошибке Python 2 mypy, а не как сообщение об ошибке Python 3 mypy. Python имеет 3 заглушки для объявление fcntl.ioctl, которое не соответствует этому сообщению об ошибке. (Вы все равно получите сообщение об ошибке с Python 3 mypy, но это будет другое сообщение.)

Во-вторых, fcntl.ioctl принимает любой объект, который поддерживает интерфейс буфера (включая вашу структуру), но mypy даже не знает, что такое интерфейс буфера. Нет аннотации для объекта, поддерживающего интерфейс буфера, и нет способа статически распознавать объекты, поддерживающие интерфейс буфера. В настоящее время невозможно правильно аннотировать такие функции, как fcntl.ioctl. Есть открытые вопросы по этому поводу, но решения нет.

Лучше всего добавить # type: ignore комментарий к этой строке.

person user2357112 supports Monica    schedule 31.07.2020
comment
Спасибо. да. Я использую mypy в режиме python2 (--py2). Я обновил вопрос, добавив дополнительную информацию. Я думаю, что ответ CristiFati об использовании ctypes.addressof () является самым простым решением. - person BrendanSimon; 03.08.2020