Как заставить Mypy работать с подклассами в функциях, как и ожидалось

У меня есть следующий код:

from typing import Callable

MyCallable = Callable[[object], int]
MyCallableSubclass = Callable[['MyObject'], int]

def get_id(obj: object) -> int:
    return id(obj)

def get_id_subclass(obj: 'MyObject') -> int:
    return id(obj)

def run_mycallable_function_on_object(obj: object, func: MyCallable) -> int:
    return func(obj)

class MyObject(object):
    '''Object that is a direct subclass of `object`'''
    pass

my_object = MyObject()

# works just fine
run_mycallable_function_on_object(my_object, get_id)

# Does not work (it runs, but Mypy raises the following error:)
# Argument 2 to "run_mycallable_function_on_object" has incompatible type "Callable[[MyObject], int]"; expected "Callable[[object], int]"
run_mycallable_function_on_object(my_object, get_id_subclass)

Поскольку MyObject наследуется от object, почему MyCallableSubclass не работает везде, где работает MyCallable?

Я немного читал о Принцип подстановки Лисков, а также ознакомился с документами Mypy о ковариантности и контравариантность. Однако даже в самих документах они приводят очень похожий пример, где говорят

Callable является примером типа, который ведет себя контравариантно в отношении типов аргументов, а именно Callable[[Employee], int] является подтипом Callable[[Manager], int].

Тогда почему использование Callable[[MyObject], int] вместо Callable[[object], int] вызывает ошибку в Mypy?

В общем у меня два вопроса:

  1. Почему это происходит?
  2. Как это исправить?

person Pro Q    schedule 07.07.2019    source источник
comment
Если вы собираетесь понизить этот вопрос, пожалуйста, дайте мне знать, как его улучшить. Я был на StackOverflow некоторое время, и это кажется мне очень серьезным и ясным вопросом, поэтому конструктивная критика будет очень признательна.   -  person Pro Q    schedule 07.07.2019


Ответы (1)


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

Что происходит?

Обратите внимание на последний пример из документации Mypy:

Callable является примером типа, который ведет себя контравариантно в отношении типов аргументов, а именно Callable[[Employee], int] является подтипом Callable[[Manager], int].

Здесь Manager подклассы из Employee. То есть, если что-то ожидает функцию, которая может принимать менеджеров, ничего страшного, если функция, которую она получает, слишком обобщает и может принимать любого сотрудника, потому что она обязательно примет менеджеров.

Однако в нашем случае MyObject подклассы от object. Таким образом, если что-то ожидает функцию, которая может принимать объекты, то это не нормально, если функция, которую она получает, переопределяет и может принимать только MyObjects.

Почему? Представьте класс с именем NotMyObject, который наследуется от object, но не наследуется от MyObject. Если функция должна иметь возможность принимать любой объект, она должна иметь возможность принимать как NotMyObjects, так и MyObjects. Однако специфическая функция может принимать только MyObjects, поэтому в данном случае она не сработает.

Как это исправить?

Майпи прав. Вам нужно иметь более конкретную функцию (MyCallableSubclass) в качестве типа, иначе либо ваш код может содержать ошибки, либо вы печатаете неправильно.

person Pro Q    schedule 07.07.2019