mypy AnyStr дает несовместимые типы в назначении на тривиальном примере

учитывая следующий класс:

from typing import AnyStr

class A(object):

    def __init__(self, param):
        # type: (AnyStr) -> None
        self.a = param # type: AnyStr

Получаю следующий результат:

$ mypy . -v
LOG:  Mypy version 0.521
LOG:  Build finished in 1.199 seconds with 10 modules, 2076 types, and 2 errors
test.py:8: error: Incompatible types in assignment (expression has type "str", variable has type "AnyStr")
test.py:8: error: Incompatible types in assignment (expression has type "bytes", variable has type "AnyStr"

Почему эта операция присваивания дает несовместимый тип?


person Yeti    schedule 27.07.2017    source источник


Ответы (1)


Я не разбираюсь в своих проблемах, но, немного поработав детективом, я думаю, что понял это.

Эта проблема

Кажется, это работает нормально, если AnyStr передается функции, но не работает, когда переменная набирается как AnyStr. Например, кажется, что это нормально работает:

from typing import AnyStr

def f(a):
    # type: (AnyStr) -> AnyStr
    return a

if __name__ == "__main__":
    print(f('cat'))
    print(f(b'dog'))

Но это не удается:

from typing import AnyStr

c = 3   # type: AnyStr

С ошибкой:

mypy_anystr.py:3: error: Invalid type "typing.AnyStr"

Это имеет смысл, потому что идея AnyStr взята из документации, заключается в том, что она предназначена быть либо str, либо bytes, но она должна быть согласованной в пределах объема вызова данной функции. Вот пример использования AnyStr:

def concat(a, b):
    #type: (AnyStr, AnyStr) -> AnyStr
    return a + b

concat('one', 'two')        # OK
concat(b'three', b'four')   # OK
concat('five', b'six')      # Error

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

Решение

В зависимости от того, чего вы действительно хотите достичь, здесь есть несколько решений. Если вы действительно агностик между str и bytes, вы можете использовать Union[Text, bytes]:

от ввода import Union, Text, AnyStr

class A:
    def __init__(self, a):
        #type: (AnyStr) -> None
        self.param = a  # type: Union[Text, bytes]

Обратите внимание, что в этом случае я использовал AnyStr на входе, но в этом случае он эквивалентен Union[Text, bytes], поскольку есть только один параметр. В качестве альтернативы, если вы действительно действительно заботитесь о том, является ли параметр str или bytes, вы можете просто взять AnyStr и заранее преобразовать его в нужную вам версию:

from typing import Union, Text, AnyStr
from six import binary_type
class A:
    def __init__(self, a):
        #type: (AnyStr) -> None
        if isinstance(a, binary_type):
            b = a.decode()  # type: Text
        else:
            b = a

        self.param = b  # type: Text

Обратите внимание, что это может показаться странным, если a закодирован в странной локали или что-то в этом роде, поэтому имейте в виду, что это упрощенный пример и YMMV, если вы пытаетесь активно декодировать объекты bytes.

person Paul    schedule 27.07.2017