Почему список списков не является списком последовательностей? - PullRequest
0 голосов
/ 09 июля 2019

Я создал следующий пример:

from typing import List, Sequence

class Circle:
    pass

def foo(circle: Circle) -> Sequence[Circle]:
    return_value: List[Circle] = [circle]
    return return_value

def bar(circle: Circle) -> List[Sequence[Circle]]:
    # Incompatible return value type (got "List[List[Circle]]", expected "List[Sequence[Circle]]")
    return_value: List[List[Circle]] = [[circle]]
    return return_value

Почему нормально возвращать List[Circle], когда он ожидает Sequence[Circle], а не List[List[Circle]], когда он ожидает List[Sequence[Circle]]?

Более конкретно, почему это не нормально, если значение является возвращаемым значением? Я думаю, что понимаю, почему это не так, как параметр, но я не понимаю, почему это значение не принимается как возвращаемое значение.

Документы дают отличный пример, показывающий, почему List s являются инвариантами:

class Shape:
    pass

class Circle(Shape):
    def rotate(self):
        ...

def add_one(things: List[Shape]) -> None:
    things.append(Shape())

my_things: List[Circle] = []
add_one(my_things)     # This may appear safe, but...
my_things[0].rotate()  # ...this will fail

Здесь идея в том, что если вы возьмете свой List[Subclass] и передадите его чему-то, что думает, что это List[Superclass], функция может отредактировать ваш List[Subclass] так, чтобы он содержал Superclass элементов, то есть становится a List[Superclass] после запуска функции.

Однако, как возвращаемое значение, я не понимаю, почему это проблема. Как только он выйдет из этой функции, все будут воспринимать его как List[Sequence[Circle]], то есть не должно быть проблем.

1 Ответ

0 голосов
/ 09 июля 2019

Еще раз, набирая этот вопрос, я думаю, что нашел ответ на него.

Рассмотрим следующий случай:

from typing import List, Sequence

class Circle:
    pass

def baz(circle_list_matrix: List[List[Circle]]) -> List[Sequence[Circle]]:
    # Incompatible return value type (got "List[List[Circle]]", expected "List[Sequence[Circle]]")
    return circle_list_matrix

Здесь Mypy абсолютно правПоднимите ошибку, потому что другие функции, которые используют circle_list_matrix, могут зависеть от того, что это List[List[Circle]], но другие функции впоследствии могут изменить его на List[Sequence[Circle]].

, чтобы определить, какиеВ этом случае Mypy придется отслеживать, когда были объявлены наши переменные, и гарантировать, что ничто не зависит от обработки возвращаемого значения как List[List[Circle]] после того, как функция вернет (даже если этоперед тем, как разрешить нам использовать его в качестве возвращаемого значения.

(Обратите внимание, что обработка его как List[List[Circle]] до возврата из функции не должна быть плохой вещью, поскольку это List[List[Circle]]в этих точках. Также, если бы он всегда обрабатывался так, как если бы он был List[Sequence[Circle]], тогда мы могли бы просто напечатать его как таковой без проблем. Возникает вопрос, когда что-то обрабатывается как List[List[Circle]], например, с circle_list_matrix[0].append(Circle()), поэтому мы должны ввести его как List[List[Circle]], чтобы выполнить эту операцию, но затем он обрабатывается как List[Sequence[Circle]] каждый раз после возврата из функции.)

Суть в том, что Mypyне делает такой анализ.Итак, чтобы Mypy знала, что это нормально, мы должны просто привести ее.

Другими словами, мы знаем, что возвращаемое значение никогда не будет снова использоваться как List[List[Circle]], поэтому bazдолжен быть записан в виде:

def baz(circle_list_matrix: List[List[Circle]]) -> List[Sequence[Circle]]:
    # works fine
    return cast(List[Sequence[Circle]], circle_list_matrix)

, где cast импортируется из typing.

Тот же самый метод приведения может применяться к bar в коде вопроса.

...