¿Cómo debo utilizar la sugerencia de tipo opcional?

84

Estoy tratando de entender cómo usar la Optionalsugerencia de tipo. De PEP-484 , sé que puedo usar Optionalpara def test(a: int = None)como def test(a: Union[int, None])o def test(a: Optional[int]).

Pero, ¿qué tal seguir ejemplos?

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

Si Optional[type]parece significar lo mismo que Union[type, None], ¿por qué debería usarlo Optional[]?

jacobcan118
fuente

Respuestas:

120

Optional[...]es una notación abreviada para Union[..., None]decirle al verificador de tipos que se requiere o None se requiere un objeto del tipo específico . ...significa cualquier sugerencia de tipo válida , incluidos los tipos compuestos complejos o a Union[]de más tipos. Siempre que tenga un argumento de palabra clave con un valor predeterminado None, debe usar Optional.

Entonces, para sus dos ejemplos, tiene tipos de contenedor dicty list, pero el valor predeterminado para el aargumento de palabra clave muestra que también Noneestá permitido, así que use 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

Tenga en cuenta que técnicamente no hay diferencia entre usar Optional[]en a Union[], o simplemente agregar Nonea Union[]. Entonces Optional[Union[str, int]]y Union[str, int, None]son exactamente lo mismo.

Personalmente, me quedaría con el uso siempreOptional[] al configurar el tipo de un argumento de palabra clave que se usa = Nonepara establecer un valor predeterminado, esto documenta la razón por la que Nonese permite mejor. Además, hace que sea más fácil mover la Union[...]parte a un alias de tipo separado, o eliminar posteriormente la Optional[...]parte si un argumento se vuelve obligatorio.

Por ejemplo, digamos que tienes

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.

    """

luego la documentación se mejora al extraer el Union[str, int]en un alias de tipo:

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.

    """

El refactor para mover el Union[]a un alias se hizo mucho más fácil porque Optional[...]se usó en lugar de Union[str, int, None]. El Nonevalor no es un 'ID de subwidget' después de todo, no es parte del valor, Noneestá destinado a marcar la ausencia de un valor.

Nota al margen: a menos que su código solo sea compatible con Python 3.9 o más reciente, desea evitar el uso de los tipos de contenedor de biblioteca estándar en las sugerencias de tipo, ya que no puede decir nada sobre qué tipos deben contener. Entonces, en lugar de dicty list, use typing.Dicty typing.List, respectivamente. Y cuando solo lee de un tipo de contenedor, también puede aceptar cualquier tipo de contenedor abstracto inmutable; las listas y tuplas son Sequenceobjetos, mientras que dictes un Mappingtipo:

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

En Python 3.9 y versiones posteriores, todos los tipos de contenedores estándar se han actualizado para admitir su uso en sugerencias de tipo, consulte PEP 585 . Pero , aunque ahora puede usar dict[str, int]o list[Union[int, str]], es posible que desee usar las anotaciones Mappingy más expresivas Sequencepara indicar que una función no mutará el contenido (se tratan como 'solo lectura'), y que las funciones funcionarían con cualquier objeto que funcione como mapeo o secuencia, respectivamente.

Martijn Pieters
fuente
@MartijnPieters ¿No necesitamos importar Dicty Listde escribir y escribir Optional[Dict]y en Optional[List]lugar de Optional[dict]...?
Alireza
@Alireza sí, y ya lo digo en mi respuesta. Busque: Nota al margen: Sin embargo, desea evitar el uso de los tipos de contenedor de biblioteca estándar en las sugerencias de tipo, ya que no puede decir nada sobre qué tipos deben contener
Martijn Pieters
Corrígeme si me equivoco, pero 3.9 permite listy dictpuede usarse para sugerencias de tipo, (vs. List, Dict). python.org/dev/peps/pep-0585
user48956
2
@ user48956: He añadido una sección sobre 3.9.
Martijn Pieters
3

Directamente desde los documentos del módulo de mecanografía mypy .

  • “Opcional [str] es solo una abreviatura o alias de Union [str, None]. Existe principalmente como una conveniencia para ayudar a que las firmas de funciones se vean un poco más limpias ".
el775
fuente