web-dev-qa-db-fra.com

Python Astuces de type: Spécification d'un type pour être une liste de nombres (ints et / ou flottants)?

Comment puis-je spécifier qu'une fonction peut prendre une liste de nombres qui peuvent être des entiers ou des flottants?

J'ai essayé de créer un nouveau type en utilisant Union comme ceci:

num = Union[int, float]

def quick_sort(arr: List[num]) -> List[num]:
    ...

Cependant, mypy n'a pas aimé ceci:

 quickSortLomutoFirst.py:32: error: Argument 1 to "quickSortOuter" has
 incompatible type List[int]; expected List[Union[int, float]]  

Existe-t-il un type qui englobe les pouces et les flotteurs?

19
Solomon Bothwell

La réponse courte à votre question est que vous devez utiliser TypeVars ou Sequence - en utilisant List[Union[int, float]] pourrait potentiellement introduire un bogue dans votre code!

En bref, le problème est que les listes sont invariantes selon le système de type PEP 484 (et dans de nombreux autres systèmes de types - par exemple Java, C # ... ). Vous essayez d'utiliser cette liste comme si elle était covariante à la place. Vous pouvez en savoir plus sur la covariance et l'invariance ici et ici , mais peut-être qu'un exemple de la raison pour laquelle votre code est potentiellement non-sûr peut être utile.

Considérez le code suivant:

from typing import Union, List

Num = Union[int, float]

def quick_sort(arr: List[Num]) -> List[Num]:
    arr.append(3.14)  # We deliberately append a float
    return arr

foo = [1, 2, 3, 4]  # type: List[int]

quick_sort(foo)

# Danger!!!
# Previously, `foo` was of type List[int], but now
# it contains a float!? 

Si ce code était autorisé à taper, nous venons de casser notre code! Tout code qui repose sur foo étant exactement de type List[int] allait maintenant se casser.

Ou plus précisément, même si int est un sous-type légitime de Union[int, float], cela ne signifie pas que List[int] est un sous-type de List[Union[int, float]], ou vice versa.


Si nous sommes d'accord avec ce comportement (nous sommes d'accord avec quick_sort décidant d'injecter des entiers ou des flottants arbitraires dans le tableau d'entrée), le correctif consiste à annoter manuellement foo avec List[Union[int, float]]:

foo = [1, 2, 3, 4]  # type: List[Union[int, float]]

# Or, in Python 3.6+
foo: List[Union[int, float]] = [1, 2, 3, 4]

Autrement dit, déclarez à l'avance que foo, bien qu'il ne contienne que des ints, est également destiné à contenir des flottants. Cela nous empêche d'utiliser incorrectement la liste après quick_sort est appelé, évitant complètement le problème.

Dans certains contextes, c'est peut-être ce que vous voulez faire. Pour cette méthode cependant, probablement pas.


Si nous ne sommes pas d'accord avec ce comportement et voulons quick_sort pour conserver les types qui figuraient à l'origine dans la liste, deux solutions viennent à l'esprit:

La première consiste à utiliser un type covariant au lieu de list - par exemple, Sequence :

from typing import Union, Sequence

Num = Union[int, float]

def quick_sort(arr: Sequence[Num]) -> Sequence[Num]:
    return arr

Il s'avère que Sequence ressemble plus ou moins à List, sauf qu'il est immuable (ou plus précisément, l'API de Sequence ne contient aucun moyen de vous permettre de muter la liste). Cela nous permet de contourner en toute sécurité le bug que nous avions au-dessus.

La deuxième solution consiste à taper votre tableau plus précisément et à insister pour qu'il doit contenir tous les entiers ou tous les flottants, interdisant un mélange des deux. Nous pouvons le faire en utilisant TypeVars avec des restrictions de valeur :

from typing import Union, List, TypeVar 

# Note: The informal convention is to prefix all typevars with
# either 'T' or '_T' -- so 'TNum' or '_TNum'.
TNum = TypeVar('TNum', int, float)

def quick_sort(arr: List[TNum]) -> List[TNum]:
    return arr

foo = [1, 2, 3, 4]  # type: List[int]
quick_sort(foo)

bar = [1.0, 2.0, 3.0, 4.0]  # type: List[float]
quick_sort(foo)

Cela nous empêchera également de "mélanger" accidentellement des types comme nous l'avons fait ci-dessus.

Je recommanderais d'utiliser la deuxième approche - elle est un peu plus précise et vous empêchera de perdre des informations sur le type exact qu'une liste contient lorsque vous la passez dans votre fonction de tri rapide.

31
Michael0x2a

De PEP 484 , qui proposait des indices de type:

Plutôt que d'exiger que les utilisateurs écrivent des numéros d'importation, puis utilisent numbers.Float etc., ce PEP propose un raccourci simple et presque aussi efficace: lorsqu'un argument est annoté comme ayant le type float, un argument de type int est acceptable ...

Ne vous embêtez pas avec les Unions. Tenez-vous simplement à Sequence[float].

Edit: Merci à Michael d'avoir pris la différence entre List et Sequence.

12
Arya McCarthy