Как мне использовать подсказку необязательного типа?

95

Я пытаюсь понять, как использовать Optionalподсказку типа. Из PEP-484 , я знаю , что я могу использовать Optionalдля def test(a: int = None)либо def test(a: Union[int, None])или def test(a: Optional[int]).

Но как насчет следующих примеров?

def test(a : dict = None):
    #print(a) ==> {'a': 1234}
    #or
    #print(a) ==> None

def test(a : list = None):
    #print(a) ==> [1,2,3,4, 'a', 'b']
    #or
    #print(a) ==> None

Если Optional[type]кажется, что означает то же самое Union[type, None], зачем мне Optional[]вообще использовать ?

jacobcan118
источник

Ответы:

134

Optional[...]- это сокращенное обозначение Union[..., None], говорящее средству проверки типов, что либо требуется объект определенного типа, либо None требуется. ...обозначает любую допустимую подсказку типа , включая сложные составные типы или Union[]несколько типов. Всякий раз, когда у вас есть аргумент ключевого слова со значением по умолчанию None, вы должны использовать Optional.

Итак, для ваших двух примеров у вас есть типы контейнеров dictи list, но значение по умолчанию для aаргумента ключевого слова показывает, что Noneэто также разрешено, поэтому используйте Optional[...]:

from typing import Optional

def test(a: Optional[dict] = None) -> None:
    #print(a) ==> {'a': 1234}
    #or
    #print(a) ==> None

def test(a: Optional[list] = None) -> None:
    #print(a) ==> [1, 2, 3, 4, 'a', 'b']
    #or
    #print(a) ==> None

Обратите внимание, что технически нет разницы между использованием Optional[]в Union[]или просто добавлением Noneв Union[]. Так Optional[Union[str, int]]и Union[str, int, None]есть в точности то же самое.

Лично я бы всегда придерживался использования Optional[]при установке типа аргумента ключевого слова, который используется = Noneдля установки значения по умолчанию, это Noneлучше документирует причину, почему это разрешено. Более того, это упрощает перемещение Union[...]части в псевдоним отдельного типа или последующее удаление Optional[...]части, если аргумент становится обязательным.

Например, скажем, у вас есть

from typing import Optional, Union

def api_function(optional_argument: Optional[Union[str, int]] = None) -> None:
    """Frob the fooznar.

    If optional_argument is given, it must be an id of the fooznar subwidget
    to filter on. The id should be a string, or for backwards compatibility,
    an integer is also accepted.

    """

то документация улучшается путем вытаскивания Union[str, int]псевдонима типа:

from typing import Optional, Union

# subwidget ids used to be integers, now they are strings. Support both.
SubWidgetId = Union[str, int]


def api_function(optional_argument: Optional[SubWidgetId] = None) -> None:
    """Frob the fooznar.

    If optional_argument is given, it must be an id of the fooznar subwidget
    to filter on. The id should be a string, or for backwards compatibility,
    an integer is also accepted.

    """

Рефакторинг для Union[]преобразования псевдонима стал намного проще, потому что Optional[...]он использовался вместо Union[str, int, None]. В Noneконце концов, значение не является «идентификатором субвиджета», оно не является частью значения, Noneоно предназначено для обозначения отсутствия значения.

Боковое примечание: если ваш код не должен поддерживать только Python 3.9 или новее, вам следует избегать использования стандартных библиотечных типов контейнеров в подсказках типов, поскольку вы не можете ничего сказать о том, какие типы они должны содержать. Таким образом , вместо dictи list, использования typing.Dictи typing.List, соответственно. И при чтении только из контейнерного типа вы можете с тем же успехом принять любой неизменяемый абстрактный тип контейнера; списки и кортежи являются Sequenceобъектами, а dictэто Mappingтип:

from typing import Mapping, Optional, Sequence, Union

def test(a: Optional[Mapping[str, int]] = None) -> None:
    """accepts an optional map with string keys and integer values"""
    # print(a) ==> {'a': 1234}
    # or
    # print(a) ==> None

def test(a: Optional[Sequence[Union[int, str]]] = None) -> None:
    """accepts an optional sequence of integers and strings
    # print(a) ==> [1, 2, 3, 4, 'a', 'b']
    # or
    # print(a) ==> None

В Python 3.9 и выше все стандартные типы контейнеров были обновлены для поддержки использования их в подсказках типов, см. PEP 585 . Но , хотя теперь вы можете использовать dict[str, int]или list[Union[int, str]], вы все равно можете использовать более выразительные Mappingи Sequenceаннотации, чтобы указать, что функция не будет изменять содержимое (они обрабатываются как `` только для чтения '') и что функции будут работать с любой объект, который работает как отображение или последовательность соответственно.

Мартейн Питерс
источник
@MartijnPieters Разве нам не нужно импортировать Dictи Listпечатать, и писать, Optional[Dict]и Optional[List]вместо Optional[dict]...
Алиреза,
@Alireza, да, и я уже заявляю об этом в своем ответе. Ищите: Примечание: вы хотите избежать использования стандартных библиотечных типов контейнеров при хинтинге типов, так как вы ничего не можете сказать о том, какие типы они должны содержать
Мартин Питерс
Поправьте меня , если я ошибаюсь, но 3,9 позволяет listи dictбыть используем для подсказок типа, ( по сравнению с List, Dict). python.org/dev/peps/pep-0585
user48956
2
@ user48956: Я добавил раздел о 3.9.
Martijn Pieters
4

Непосредственно из документации модуля mypy typing .

  • «Необязательный [str] - это просто сокращение или псевдоним для Union [str, None]. Он существует в основном для удобства, чтобы сигнатуры функций выглядели немного чище ».
the775
источник