почему mypy `cast` работает только иногда?

Сначала я сделал глупый маленький класс

# cheese_helpers.py
class Cheese:
    pass

Потом это случилось

# weird.py

import lxml
from typing import cast, List
import cheese_helpers

o: List[Any] = []
reveal_type(o) # builtins.List[Any] as expected

y = cast(List[cheese_helpers.Cheese], o)  
reveal_type(y)  # builtins.List[cheese_helpers.Cheese], as expected

# so far so good. And then:

z = cast(List[lxml.html.HtmlElement], o)  
reveal_type(z) # builtins.List[Any] ???????????????

Эта последняя строка должна быть List[lxml.html.HtmlElement], если вы спросите меня. Сыр тоже не снабжен аннотациями, и это прекрасно работает.

Я уверен, что для того, чтобы последняя строка заработала, мне нужно получить / сделать несколько аннотаций lxml. Но мне кажется очень странным, что мой cast полностью игнорируется. Я выбрал класс Cheese, и он работает. Я привел к классу HtmlElement, но это не так.

У меня вопрос почему?


person Sheena    schedule 25.09.2018    source источник
comment
Быстрая проверка: что, если вы import lxml.html, а не просто import lxml? Ваш текущий импорт кажется неверным; вам нужно явно импортировать подмодуль.   -  person user2357112 supports Monica    schedule 26.09.2018


Ответы (1)


Возможно, вы используете старую версию mypy? Когда я пытаюсь проверить ваш код как с помощью mypy 0.630 (последняя версия на pypi), так и с последним кодом в их главной ветке git, я получаю обнаруженный тип builtins.list[Any] во всех трех случаях.

Надеемся, что этот раскрытый тип немного более интуитивно понятен - проблема в том, что, к сожалению, нет доступных заглушек для библиотеки lxml на typehed, что означает, что mypy не имеет информации о том, что html.HtmlElement на самом деле. (Насколько известно mypy, это может быть класс, функция, переменная, псевдоним типа, именованный набор ...)

Итак, он сдается и просто предполагает, что имеет тип Any.

Это также объясняет, почему назначение выхода get_some_relevant_elements на List[bool] работает без ошибок. Переменная типа List[Any] теоретически может содержать что угодно, включая bools, поэтому возможно это безопасное присвоение.


В любом случае, если вам не нравится такое поведение, у вас есть два варианта:

  1. Примите тот факт, что библиотека lxml не имеет подсказок типов / типизирована чисто динамически, и разработайте свой код так, чтобы весь этот динамизм содержался в одном месте. По мере извлечения информации из файлов XML (необязательно) проверяйте их и возвращайте собственные пользовательские классы с аннотациями. По сути, намеренно устанавливайте барьер между динамической и нединамической частями вашей кодовой базы.

  2. Создайте свои собственные заглушки для lxml. Эти заглушки не обязательно должны быть сложными - может быть достаточно просто создать предварительные заглушки для нескольких классов и методов, которые вам нужны. (И если они в конечном итоге станут достаточно конкретизированными, вы можете открыть их исходный код и внести свой вклад в сообщество, если вы так склонны.)

person Michael0x2a    schedule 25.09.2018
comment
Спасибо за ответ, но на самом деле он бесполезен. Я резко изменил свой вопрос, чтобы увеличить масштаб проблемы - person Sheena; 26.09.2018
comment
@Sheena - суть моего ответа все еще актуальна, даже после вашего редактирования. Доступных lxml заглушек нет, поэтому mypy предполагает, что все, что вы импортируете из него, имеет тип Any. В частности, предполагается, что переменная lxml.html.HtmlElement является псевдонимом типа, который точно эквивалентен Any, а не классу, поэтому приведение не выполняется. Напротив, если вы добавите какой-либо файл в свой локальный проект, mypy проанализирует его + сделает из него все, что может, независимо от того, содержит ли он аннотации типов или нет. - person Michael0x2a; 26.09.2018
comment
Это решение не анализировать сторонние библиотеки, если они явно не соглашаются на типы, является преднамеренным - часто бывает, что сторонние библиотеки написаны очень динамично / просто сбивают с толку mypy. В этом конкретном случае mypy не сможет анализировать lxml, даже если бы захотел - lxml написан на cython и распространяется как двоичный, а mypy не может понимать произвольные двоичные файлы. - person Michael0x2a; 26.09.2018