Действительно ли «None» и «type(None)» эквивалентны для анализа типов?

Согласно части PEP 484 «Использование None»:

При использовании в подсказке типа выражение None считается эквивалентным type(None).

Однако я столкнулся со случаем, когда оба не кажутся эквивалентными:

from typing import Callable, NamedTuple, Type, Union

# I define a set of available return types:
ReturnType = Union[
    int,
    None,
]

# I use this Union type to define other types, like this callable type.
SomeCallableType = Callable[..., ReturnType]

# But I also want to store some functions metadata (including the function's return type) in a `NamedTuple`:
class FuncInfos(NamedTuple):
    return_type: Type[ReturnType]

# This works fine:
fi_1 = FuncInfos(return_type=int)

# But this issues an error:
# main.py:21: error: Argument "return_type" to "FuncInfos" has incompatible type "None"; expected "type[int] | type[None]"  [arg-type]
# Found 1 error in 1 file (checked 1 source file)
fi_2 = FuncInfos(return_type=None)

# But this works fine:
fi_3 = FuncInfos(return_type=type(None))

Для меня не составляет особых проблем написать type(None), а не просто None, но мне бы хотелось понять приведенную выше ошибку, которая, похоже, противоречит цитате из PEP 484.

Фрагмент доступен для исполнения здесь.


Обновлено: На самом деле кажется, что это сводится к следующему:

from typing import Type

a: Type[None]

# This seems to cause an issue:
# main.py:4: error: Incompatible types in assignment (expression has type "None", variable has type "type[None]")  [assignment]
# Found 1 error in 1 file (checked 1 source file)
a = None

# This seems to work:
a = type(None)

Фрагмент доступен для исполнения здесь.

«При использовании в подсказке типа» return_type не является подсказкой типа, это переменная класса, которая содержит тип. None обычно не является типом; это объект (единственный объект) типа NoneType. Только в контексте подсказки типа это имеет особое значение.

Miles Budnek 19.06.2024 02:11

@MilesBudnek Разве следующий : Type[None]return_type не все еще является подсказкой типа? Просто None и type(None) — это два значения разных типов, оба нельзя использовать как взаимозаменяемые?

vmonteco 19.06.2024 02:24
Type[None] и type(None) — это не одно и то же. Как сказано в предложении, которое вы процитировали, None в подсказке типа эквивалентно type(None), поэтому Type[None] эквивалентно Type[type(None)]. Это означает, что a в вашем редактировании является типом (в частности NoneType), а None не является типом.
Miles Budnek 19.06.2024 02:34

Отвечает ли это на ваш вопрос? Подсказка типа Python 3 для None?

InSync 19.06.2024 03:25

Я бы сверился с актуальными спецификациями типизации , но даже в этом случае формулировка при использовании в подсказке типа выражение None считается эквивалентным type(None) довольно неясно. Конечно, type(None) никогда не может использоваться в подсказке типа — это выражение вызова (и, следовательно, не выражение аннотации).

dROOOze 19.06.2024 03:40
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
5
138
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Да, это было интересно.

Часть 1: Ничего странного

Я бы сказал, что все сводится к следующему: None на самом деле является экземпляром NoneType, но для практических целей его следует рассматривать как None. Учтите следующее:

from types import NoneType
x: None = None # succeeds
y: NoneType = None # succeeds in pyright, fails in mypy
z: type[None] = None # fails in both

Странно, правда? Второй случай проходит по пирайту, поскольку в нем говорится, что None является экземпляром NoneType, но в mypy специальный случай явно запрещен: «NoneType не следует использовать в качестве типа, вместо этого используйте None»

Часть 2. Псевдоним типа требует слишком многого от объединения

Кажется, что когда вы определяете союз, который использует None, он предполагает, что type[None] является частью союза:

from typing import reveal_type

x = int | None
y = None
reveal_type(x) # type[int] | type[None]
reveal_type(y) # None

Мне это кажется ошибкой — я ожидал, что show_type на x покажет:

reveal_type(x) # type[int] | None

...поскольку, как показано выше, type[None] не совсем канонический тип. Но это то, что есть, поэтому нам пока придется с этим работать.

Часть 3. А как насчет варианта использования?

Мне пришло в голову, что инспекция уже реализует подобную функцию, поэтому я бы посмотрел, какие типы имеют следующие:

from inspect import signature

def test() -> int:
    return 1

x = signature(test)
print(x.return_annotation) # int

def test2() -> None:
    pass

y = signature(test2)
print(y.return_annotation) # None

Оказывается, это не поддерживает статическую типизацию, но правильный тип для этих аннотаций возврата будет type[int] | None. Лично я бы выделил «Нет», если это возможно:

from typing import Callable, NamedTuple

ReturnType = int | str

SomeCallableType = Callable[..., None | ReturnType]


class FuncInfos(NamedTuple):
    return_type: None | type[ReturnType]

fi_1 = FuncInfos(return_type=int)
fi_2 = FuncInfos(return_type=None)

Надеюсь это поможет!

Здравствуйте и спасибо за этот ответ. Часть 1. Пример 2: Похоже, меня мотивировала эта проблема . Я действительно не понимал, насколько семантически (я не знаю о реализации на тот момент) , предоставленный mre, был проблемой.

vmonteco 30.06.2024 02:45

В остальном я действительно нашел лучшее объяснение, чем объяснение из PEP484, которое лучше объясняет это поведение, из предыдущего Прагматики PEP (483) : «Там, где ожидается тип, тип (None) можно заменить None». ; например, Union[t1, None] == Union[t1, type(None)]."

vmonteco 30.06.2024 02:51

Похоже, это указывает на то, что во всех приведенных выше примерах, когда он указан в месте, где ожидается тип, None интерпретируется как type(None) (NoneType). Итак, из моего второго MRE: a: Type[None] = None интерпретируется как a: Type[type(None)] = None. Это означает, что a ожидает подклассы type(None)/NoneType, а не значения типа NoneType. PEP 484 может быть просто слишком резко сформулирован, поскольку None и type(None) точно не будут эквивалентны.

vmonteco 30.06.2024 02:52
Ответ принят как подходящий

Как показано в сообщении, в PEP 484 говорится что-то двусмысленное:

При использовании в подсказке типа выражение None считается эквивалентным type(None).

Но я узнал о PEP 483, который использует гораздо более четкую формулировку в своей Прагматике:

Если ожидается тип, None можно заменить на type(None); например Union[t1, None] == Union[t1, type(None)].

Учитывая это, различные примеры теперь начинают иметь смысл.

Например, мой второй MRE:

from typing import Type

a: Type[None]

# This seems to cause an issue:
# main.py:4: error: Incompatible types in assignment (expression has type "None", variable has type "type[None]")  [assignment]
# Found 1 error in 1 file (checked 1 source file)
a = None

# This seems to work:
a = type(None)

теперь можно интерпретировать как:

from typing import Type

a: Type[type(None)] # Type[...] expects a type.
a = None
a = type(None)

Теперь тип a явно является «подтипом type(None)», что точно не соответствует типу выражения None, отсюда и сообщение об ошибке. Однако на самом деле это тип второго выражения: type(None) («Каждый тип является подтипом самого себя». Согласно самому PEP 483).


Итак, как было предложено в некоторых комментариях, я в основном неверно истолковал PEP 484 и забыл, что «эквивалентность» действует только «в подсказке типа» (или «там, где ожидается тип»). Чего не было при передаче параметров или назначении в моих фрагментах.

Кроме того, Type[None] (подтип None) не имел бы особого смысла, поскольку None не является типом (отмените связь NoneType), даже несмотря на то, что MyPy использует формулировку «тип None» (я бы ожидал чего-то вроде «None имеет тип NoneType», а не «None имеет тип None»).

vmonteco 30.06.2024 03:24

Другие вопросы по теме