Wie soll ich den optionalen Typhinweis verwenden?

85

Ich versuche zu verstehen, wie man den OptionalTyphinweis verwendet. Von PEP-484 , weiß ich , ich kann Optionalfür def test(a: int = None)entweder als def test(a: Union[int, None])oder def test(a: Optional[int]).

Aber wie wäre es mit folgenden Beispielen?

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

Wenn es Optional[type]dasselbe zu bedeuten scheint Union[type, None], warum sollte ich es Optional[]überhaupt verwenden?

jacobcan118
quelle

Antworten:

121

Optional[...]ist eine Kurzschreibweise für Union[..., None], die dem Typprüfer mitteilt, dass entweder ein Objekt des bestimmten Typs erforderlich ist oder None erforderlich ist. ...steht für einen gültigen Typhinweis , einschließlich komplexer zusammengesetzter Typen oder eines oder Union[]mehrerer Typen. Wann immer Sie ein Schlüsselwortargument mit Standardwert haben None, sollten Sie verwenden Optional.

Für Ihre beiden Beispiele haben Sie also dictund listContainertypen, aber der Standardwert für das aSchlüsselwortargument zeigt, dass Nonedies ebenfalls zulässig ist. Verwenden Sie daher 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

Beachten Sie, dass es zwischen der Verwendung von technisch keinen Unterschied Optional[]auf eine Union[]oder nur das Hinzufügen Nonezu der Union[]. Also Optional[Union[str, int]]und Union[str, int, None]sind genau das gleiche.

Persönlich würde ich mich immer daran halten , Optional[]wenn der Typ für ein Schlüsselwortargument festgelegt wird, mit = Nonedem ein Standardwert festgelegt wird. Dies dokumentiert den Grund, warum dies Nonebesser zulässig ist. Darüber hinaus ist es einfacher, das Union[...]Teil in einen separaten Typalias zu verschieben oder das Optional[...]Teil später zu entfernen, wenn ein Argument obligatorisch wird.

Angenommen, Sie haben

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.

    """

Anschließend wird die Dokumentation verbessert, indem der Union[str, int]Alias ​​in einen Typ gezogen wird:

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.

    """

Der Refactor zum Verschieben des Union[]Alias ​​wurde umso einfacher, Optional[...]als er stattdessen verwendet wurde Union[str, int, None]. Der NoneWert ist schließlich keine 'Subwidget-ID', er ist nicht Teil des Werts und Nonesoll das Fehlen eines Wertes kennzeichnen .

Randnotiz: Sofern Ihr Code nicht nur Python 3.9 oder höher unterstützen muss, möchten Sie vermeiden, die Standardbibliothekscontainertypen für Typhinweise zu verwenden, da Sie nichts darüber sagen können, welche Typen sie enthalten müssen. Also statt dictund list, Verwendung typing.Dictund typing.Listsind. Und wenn Sie nur von einem Containertyp lesen , können Sie genauso gut jeden unveränderlichen abstrakten Containertyp akzeptieren. Listen und Tupel sind SequenceObjekte, während dictes sich um einen MappingTyp handelt:

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

In Python 3.9 und höher wurden alle Standardcontainertypen aktualisiert, um die Verwendung in Typhinweisen zu unterstützen (siehe PEP 585) . Aber , während Sie jetzt können verwenden dict[str, int]oder list[Union[int, str]]Sie noch möchten die ausdrucksvoller verwenden Mappingund SequenceAnmerkungen , um anzuzeigen , dass eine Funktion nicht die Inhalte werden mutiert (sie behandelt werden als ‚nur lesen‘), und dass die Funktionen funktionieren würde mit Jedes Objekt, das als Mapping bzw. Sequenz fungiert.

Martijn Pieters
quelle
@MartijnPieters Müssen wir nicht importieren Dictund tippen Listund schreiben Optional[Dict]und Optional[List]statt Optional[dict]...
Alireza
@ Alireza ja, und das sage ich schon in meiner Antwort. Suchen Sie nach: Randnotiz: Sie möchten jedoch vermeiden, die Standardtypen von Bibliothekscontainern für Typhinweise zu verwenden, da Sie nichts darüber sagen können, welche Typen sie enthalten müssen
Martijn Pieters
Korrigieren Sie mich, wenn ich falsch liege, aber 3.9 erlaubt listund dictkann für Typhinweise verwendet werden (vs. List, Dict). python.org/dev/peps/pep-0585
user48956
2
@ user48956: Ich habe einen Abschnitt zu 3.9 hinzugefügt.
Martijn Pieters
3

Direkt aus den Dokumenten des mypy-Tippmoduls .

  • „Optional [str] ist nur eine Abkürzung oder ein Alias ​​für Union [str, None]. Es dient hauptsächlich dazu, Funktionssignaturen ein wenig sauberer aussehen zu lassen. “
the775
quelle