Як я повинен використовувати підказку щодо необов’язкового типу?


85

Я намагаюся зрозуміти, як використовувати 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[]?

Відповіді:


121

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

@Alireza так, і я це вже зазначив у своїй відповіді. Шукайте: Бічна примітка: Ви хочете уникати використання стандартних типів контейнерів бібліотеки в натякуванні на типи, однак ви не можете сказати нічого про те, які типи вони повинні містити
Martijn Pieters

Виправте мене, якщо я помиляюся, але 3.9 дозволяє listі dictвикористовувати для підказок типу (проти List, Dict). python.org/dev/peps/pep-0585
user48956

2
@ user48956: Я додав розділ на 3.9.
Мартін Пітерс

4

Безпосередньо з mypy typing module docs .

  • “Необов’язковий [str] - це просто скорочення або псевдонім для Union [str, None]. Це існує переважно як зручність, щоб допомогти підписам функцій виглядати трохи чистішими ».
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.