Есть ли в Python способ реализовать приведение типов, которое автоматически выбирает правильный тип?
Скажем, у вас есть класс:
class Foo:
foo: list[int]
def __init__(self):
self.foo = cast(list[int], api.get())
Вы знаете, что api.get() всегда возвращает list[int], но это странная библиотека со случайными подсказками типов, поэтому она утверждает, что это (скажем) FunkyContainerType, когда это не так. Кроме того, сегодня вам не стоит беспокоиться о создании правильного файла-заглушки для библиотеки из-за сроков.
self.foo = cast(list[int], api.get()) создает в коде много визуального шума, что также является избыточным, если тип self.foo уже определен. Более того, если переменная имеет собственный тип, вам придется импортировать этот тип во время выполнения, чтобы программа не аварийно завершилась, поэтому вы не можете поместить ее в файл-заглушку и чаще будете сталкиваться с проблемами при циклическом импорте.
Я бы предпочел использовать self.foo = cast(api.get()), который определяет тип self.foo и преобразует результат вызова API к этому типу.
Таким образом, ключевыми пожеланиями являются (1) не указывать тип избыточно, (2) ничего не менять в поведении во время выполнения, т. е. не импортировать типы во время выполнения, и (3) минимальный визуальный беспорядок.
Примечания: (1) Этот вопрос именно о том, о чем говорится. Речь идет не о копировании или преобразовании значений во время выполнения. Речь идет о подсказках типов. (2) Использование приведения заключается в предоставлении дополнительной информации средству проверки типов, которую вы знаете, но которую средство проверки типов не может вывести.
Что плохого в просто self.foo = api.get()? Возможно self.foo: list[int] = api.get()? Добавьте аннотацию api.get, чтобы вернуть -> list[int]…?
«он определяет тип self.foo и преобразует результат вызова API к этому типу». это не имеет никакого смысла. Как вы думаете, что cast здесь делает? cast полезен только для того, чтобы сообщить проверяющему типы: «На самом деле это не тот тип, который вы думаете, это какой-то другой тип, и я, человек, скажу вам предположить, что это так»
@deceze Да на твой первый вопрос. Предположим, я не могу изменить get(), потому что это сторонний код и он может (в общем) возвращать что угодно. Но я знаю, что оно вернется list[int]. Т.е. на самом деле это такая ситуация, когда вы хотите либо привести, либо использовать утверждения типа.
@juanpa.arrivillaga Я думаю, cast делает именно то, что вы говорите. На самом деле меня интересует только поведение при проверке типов. Я явно хочу избежать каких-либо осложнений во время выполнения (я упоминаю проблемы с циклическим импортом и желание использовать файлы-заглушки).
«Я бы предпочел использовать self.foo = cast(api.get()), который определяет тип self.foo и преобразует результат вызова API к этому типу». И как именно программа проверки типов будет определять «настоящий» тип без правильных подсказок типа и без запуска кода? По сути, вы просите его выполнить задачу времени выполнения во время проверки типов.
@InSync Цель функции cast — сообщить средству проверки типов то, что вы знаете о данных, но средство проверки типов не может сделать вывод. В этом случае я знаю, что данные list[int], но средство проверки типов — нет. (Я написал множество заглушек типов для недостаточно типизированных сторонних библиотек. Это определенно еще один вариант. Но сегодня у меня возникли вопросы не об этом.)






# type: ignore или лучше (для Пайрайта) # pyright: ignore [reportAssignmentType] сделай свое дело.
Присвоение не меняет и не расширяет тип атрибута. (H/t juanpa.arrivillaga.)
Что также работает:
self.foo = cast(Any, api.get())
Он не меняет тип self.foo на list[int] | Any, как я ожидал, но дает желаемый результат (не меняя тип). Но сейчас мне это нравится меньше, потому что для меня это менее очевидно и, на мой взгляд, добавляет больше беспорядка.
Any по сути сигнализирует «перестаньте заботиться о типах здесь», что устраняет необходимость подсказки типов.
Я думаю тебе просто нужно # type: ignore
Тип self.foo — type(self.foo). Единственный способ изменить его тип на list — это list(self.foo). Но здесь вы говорите о подсказках типов. Подсказки типов — это всего лишь подсказки, они не изменяют фактические значения.
@deceze Я тоже так думал, но попробуй. У меня это работает с Пайрайтом.
@Jeyekomon Я говорю только о подсказках по типу. Значение имеет тип list[int]; программа проверки типов просто этого не знает. Я не хочу копировать список или добавлять какие-либо другие махинации во время выполнения.
@juanpa.arrivillaga # type: ignore тоже работает! Я предпочитаю # pyright: ignore [reportAssignmentType].
Определите «работает для меня». Это будет «работать» постольку, поскольку не вызовет никаких нареканий, потому что вы там явно отключаете проверку типов…?!
@deceze Под «работает у меня» я имею в виду, что хочу, чтобы Пайрайт знал, что self.foo относится к типу list[int], а не Unknown, Any, list[int] | Unknown, list[int] | Any или какой-либо другой вариации. Any (и Unknown в зависимости от настроек строгости) тоже не вызовет нареканий, но мне нужна проверка типов. (cast предназначен для ситуаций, когда вы знаете что-то о данных, чего не знает Пайрайт, и хотите это сообщить. Я знаю, что self.foo — это list[int], а не Any или Unknown.)
@DawnDrescher единственное, что я хотел бы отметить, это то, что # type: ignore должен работать с любой проверкой типов, можно ли использовать обе?
@juanpa.arrivillaga К сожалению, это не совсем так, но в данном проекте где-то будет конфигурация, указывающая, какая программа проверки типов используется сопровождающими проекта. Я использую Pyright, поэтому, используя только комментарий Pyright, я все равно буду видеть все другие несвязанные ошибки типа в этой строке. С обоими комментариями (если это вообще синтаксически возможно), более общий из них будет экранировать этот эффект, и я не увижу несвязанных ошибок типа, потому что Pyright также уважает # type: ignore. Может быть, у MyPy есть аналогичный синтаксис комментариев?
cast — это универсальный инструмент, распознавание mypy и ему подобных жестко запрограммировано. В особых случаях вы можете определить свою собственную функцию, подобную cast, которая ограничена определенным типом возвращаемого значения (и, следовательно, не требует специальной обработки со стороны средства проверки типов).
def as_list_int(x: Any) -> list[int]:
return x
class Foo:
foo: list[int]
def __init__(self):
self.foo = as_list_int(api.get())
Настоящая проблема здесь в том, что api.get должно подразумеваться как возвращающее list[int]. Если это невозможно (потому что он действительно может возвращать значения, отличные от list[int]), то вам, вероятно, все равно понадобится другая функция, чтобы проверить, что этот вызов возвращает list[int] (вызывая исключение, если это не так) или создает list[int] из того, что он делает. возвращаться.
Спасибо! Это решение работает! Мне нравится, что он использует типы только в тех местах, где они игнорируются во время выполнения (не нужно импортировать во время выполнения). Но тип list[int] по-прежнему указывается избыточно. Это также означает, что вам понадобится отдельная функция для каждого такого типа. Я все еще предпочитаю решение # pyright: ignore [reportAssignmentType]. (Мне кажется, что это более читабельно и менее удивительно, чем моя первоначальная cast(Any, ...) идея.)
Это избыточно только в том случае, если вам нужен вывод типа, а такие инструменты, как mypy, на самом деле не предназначены для этой цели. Если вы считаете, что api.get является полиморфным по возвращаемому значению (то есть действительно способным возвращать значение любого запрошенного вами типа), то вывод типа соответствует проверке типов. Однако api.get работает не так. Он действительно возвращает значение определенного типа, но тип возвращаемого значения этого не отражает.
Вы знаете, что
typing.castвообще не трансформирует никаких ценностей? Это просто дополнительная аннотация ввода. Это то, чего ты хочешь?