Jak należy korzystać z podpowiedzi typu opcjonalnego?

95

Próbuję zrozumieć, jak używać Optionalwskazówki dotyczącej typu. Od PEP-484 , wiem, że mogę używać Optionaldo def test(a: int = None)albo jako def test(a: Union[int, None])lub def test(a: Optional[int]).

Ale co z następującymi przykładami?

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

Jeśli Optional[type]wydaje się oznaczać to samo co Union[type, None], dlaczego Optional[]w ogóle powinienem używać ?

jacobcan118
źródło

Odpowiedzi:

135

Optional[...]jest skrótową notacją Union[..., None]informującą moduł sprawdzania typów, że obiekt określonego typu jest wymagany lub None jest wymagany. ...oznacza każdą prawidłową wskazówkę dotyczącą typu , w tym złożone typy złożone lub aUnion[] więcej typów. Zawsze, gdy masz argument słowa kluczowego z wartością domyślną None, powinieneś użyć Optional.

W dwóch przykładach masz typy kontenerów dicti list, ale domyślna wartość aargumentu słowa kluczowego pokazuje, że Nonejest to również dozwolone, więc użyjOptional[...] :

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

Zauważ, że nie ma technicznej różnicy między używaniem Optional[]na a Union[]lub po prostu dodawaniem Nonedo Union[]. Więc Optional[Union[str, int]]i Union[str, int, None]są dokładnie tym samym.

Osobiście wolałbym zawsze używać Optional[]podczas ustawiania typu argumentu słowa kluczowego używanego = Nonedo ustawiania wartości domyślnej, to dokumentuje powód, dla którego Nonejest lepiej dozwolony. Ponadto ułatwia przeniesienie Union[...]części do oddzielnego aliasu typu lub późniejsze usunięcie Optional[...]części, jeśli argument stanie się obowiązkowy.

Na przykład powiedz, że masz

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.

    """

następnie dokumentacja jest ulepszana poprzez wyciągnięcie Union[str, int]do aliasu typu:

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.

    """

Refaktoryzacja przenoszenia Union[]do aliasu została znacznie łatwiejsza, ponieważ Optional[...]została użyta zamiastUnion[str, int, None] . W Nonekońcu wartość nie jest „identyfikatorem subwidgetu”, nie jest częścią wartości,None jest przeznaczona do flaga braku wartości.

Uwaga boczna: O ile twój kod nie musi obsługiwać tylko Pythona 3.9 lub nowszego, chcesz uniknąć używania standardowych typów kontenerów bibliotek w podpowiedziach typu, ponieważ nie możesz nic powiedzieć o tym, jakie typy muszą zawierać. Więc zamiast dicti list, użytkowania typing.Dicti typing.Listodpowiednio. Czytając tylko z typu kontenera, równie dobrze możesz zaakceptować dowolny niezmienny abstrakcyjny typ kontenera; listy i krotki są Sequenceobiektami, a dictjest Mappingtypem:

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

W Pythonie 3.9 i nowszych wszystkie standardowe typy kontenerów zostały zaktualizowane, aby obsługiwały ich używanie we wskazówkach dotyczących typów, patrz PEP 585 . Ale teraz możesz używaćdict[str, int] albo list[Union[int, str]], nadal może chcieć użyć bardziej wyraziste Mappingi Sequenceadnotacje, aby wskazać, że funkcja nie będzie mutacji zawartość (są one traktowane jako „tylko do odczytu”), oraz że funkcje będą pracować z dowolny obiekt, który działa odpowiednio jako odwzorowanie lub sekwencja.

Martijn Pieters
źródło
@MartijnPieters Nie musimy importu Dicti Listod pisania i pisać Optional[Dict]i Optional[List]zamiast Optional[dict]...
Alireza
@Alireza tak, i już to stwierdzam w mojej odpowiedzi. Poszukaj: Uwaga dodatkowa: nie chcesz jednak używać standardowych typów kontenerów biblioteki w podpowiedziach typu, ponieważ nie możesz powiedzieć nic o tym, jakie typy muszą zawierać
Martijn Pieters
Popraw mnie, jeśli się mylę, ale 3.9 pozwala listi dictmoże być używane do podpowiedzi typu (vs. List, Dict). python.org/dev/peps/pep-0585
user48956
2
@ user48956: Dodałem sekcję na 3.9.
Martijn Pieters
4

Bezpośrednio z mypy typing module docs .

  • „Opcjonalne [str] to tylko skrót lub alias dla Union [str, None]. Istnieje głównie dla wygody, aby podpisy funkcji wyglądały trochę czysto. ”
the775
źródło