Как заставить Mypy иметь дело с подклассами в функциях, как ожидалось - PullRequest
1 голос
/ 07 июля 2019

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

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 docs по поводу ковариации и контравариантности.Однако даже в самих документах они приводят очень похожий пример, где говорят, что

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

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

В целом у меня два вопроса:

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

1 Ответ

1 голос
/ 07 июля 2019

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

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

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

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

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

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

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

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

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

...