web-dev-qa-db-fra.com

Types de paramètre de fonction dans Python

Sauf erreur de ma part, créer une fonction dans Python fonctionne comme suit:

def my_func(param1, param2):
    # stuff

Cependant, vous ne donnez pas réellement les types de ces paramètres. De plus, si je me souviens bien, Python est un langage fortement typé. En tant que tel, il semble que Python ne devrait pas vous permettre de passer un paramètre d'un type différent de celui attendu par le créateur de la fonction. . Cependant, comment Python sait-il que l'utilisateur de la fonction transmet les types appropriés? Le programme mourra-t-il si le type est incorrect, en supposant que la fonction utilise réellement le paramètre? Devez-vous spécifier le type?

235
Leif Andersen

Python est fortement typé car chaque objet a un type, chaque objet connaît son type, il est impossible d’utiliser accidentellement ou délibérément un objet de type "comme si" c’était un objet de type différent et toutes les opérations élémentaires sur l'objet sont délégués à son type.

Cela n'a rien à voir avec noms. Un nom dans Python "n'a pas de type": si et quand un nom est défini, le nom fait référence à un object , et l'objet a un type (mais cela n'a pas force en fait un type sur le nom : un nom est un nom).

Un nom dans Python peut parfaitement faire référence à différents objets à des moments différents (comme dans la plupart des langages de programmation, mais pas tous) - et il n'y a aucune contrainte sur le nom, de sorte que, s'il y a déjà été fait référence un objet de type X, il est alors toujours contraint de se référer uniquement à d'autres objets de type X. Les contraintes sur noms ne font pas partie du concept de "typage fort", bien que certains amateurs de static la frappe (où les noms ne sont pas forcés , et de manière statique, au moment de la compilation, aussi) utilise mal le terme de cette façon .

144
Alex Martelli

Les autres réponses ont fait du bon travail en expliquant la dactylographie du canard et la réponse simple en tzot :

Python n'a pas de variables, comme d'autres langages où les variables ont un type et une valeur; il a des noms pointant vers des objets, qui connaissent leur type.

Cependant , une chose intéressante a changé depuis 2010 (date à laquelle la question a été posée pour la première fois), à savoir la mise en œuvre de PEP 3107 ( implémenté dans Python 3). Vous pouvez maintenant spécifier le type d'un paramètre et le type du type de retour d'une fonction comme ceci:

def pick(l: list, index: int) -> int:
    return l[index]

Nous pouvons voir ici que pick prend 2 paramètres, une liste l et un entier index. Il devrait également renvoyer un entier.

Donc ici, il est implicite que l est une liste d'entiers que nous pouvons voir sans trop d'effort, mais pour des fonctions plus complexes, cela peut être un peu déroutant quant au contenu de la liste. Nous voulons également que la valeur par défaut de index soit 0. Pour résoudre ce problème, vous pouvez choisir d'écrire pick comme ceci:

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

Notez que nous insérons maintenant une chaîne sous le type l, ce qui est autorisé du point de vue de la syntaxe, mais il n’est pas recommandé d’analyser par programme (nous y reviendrons plus tard).

Il est important de noter que Python ne soulève pas de TypeError si vous passez un float dans index, la raison en est l'un des points principaux de la philosophie de conception de Python: "Nous sommes tous des adultes consentants" , ce qui signifie que vous devez être conscient de ce que vous pouvez transmettre à une fonction et de ce que vous ne pouvez pas. Si vous voulez vraiment écrire du code qui jette TypeErrors, vous pouvez utiliser la fonction isinstance pour vérifier que l'argument passé est du type ou une sous-classe appropriée, comme ceci:

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

Vous trouverez plus de détails sur les raisons pour lesquelles vous devriez faire cela et sur ce que vous devriez faire plutôt que dans la section suivante et dans les commentaires.

PEP 3107 n'améliore pas seulement la lisibilité du code, mais a également plusieurs cas d'utilisation adaptés que vous pouvez lire sur ici .


Les annotations de type ont beaucoup plus retenu l'attention dans Python 3.5 avec l'introduction de PEP 484 qui introduit un module standard pour les astuces de type.

Ces indications de type provenaient du vérificateur de type mypy ( GitHub ), qui est maintenant PEP 484 conforme.

Avec le module de dactylographie, vous obtenez une collection assez complète de conseils de type, notamment:

  • List, Tuple, Set, Map - pour list, Tuple, set et map, respectivement.
  • Iterable - utile pour les générateurs.
  • Any - quand cela pourrait être n'importe quoi.
  • Union - lorsqu'il peut s'agir de quoi que ce soit dans un ensemble de types spécifié, par opposition à Any.
  • Optional - quand il pourrait être Aucun. Raccourci pour Union[T, None].
  • TypeVar - utilisé avec les génériques.
  • Callable - utilisé principalement pour les fonctions, mais pourrait être utilisé pour d'autres appels.

Ce sont les indications de type les plus courantes. Une liste complète est disponible dans le documentation du module de frappe .

Voici l'ancien exemple utilisant les méthodes d'annotation introduites dans le module de typage:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

Une fonction puissante est la Callable qui vous permet de taper des méthodes annotées qui prennent une fonction en argument. Par exemple:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

L’exemple ci-dessus pourrait devenir plus précis avec l’utilisation de TypeVar à la place de Any, mais cette opération a été laissée à la discrétion du lecteur, car j’ai déjà rempli ma réponse avec trop d’informations sur les merveilleuses nouvelles fonctionnalités activées par les indications de type.


Auparavant, lorsqu'un code Python documenté avec, par exemple, Sphinx , certaines des fonctionnalités ci-dessus pouvaient être obtenues en écrivant des docstrings formatés comme ceci:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

Comme vous pouvez le constater, cela prend un certain nombre de lignes supplémentaires (le nombre exact dépend du degré de précision de votre explication et du formatage de votre docstring). Mais il devrait maintenant être clair pour vous comment PEP 3107 fournit une alternative qui est à bien des égards (toutes?) Supérieure. Ceci est particulièrement vrai en combinaison avec PEP 484 qui, comme nous l’avons vu, fournit un module standard qui définit une syntaxe pour ces indications de type/annotations pouvant être utilisée de manière non ambiguë. précis mais flexible, ce qui en fait une combinaison puissante.

À mon avis, il s'agit de l'une des meilleures fonctionnalités de Python. J'ai hâte que les gens commencent à en exploiter le pouvoir. Désolé pour la longue réponse, mais c'est ce qui se passe quand je suis excité.


Vous trouverez un exemple de code Python qui utilise beaucoup de conseils de type ici .

592
erb

Vous ne spécifiez pas un type. La méthode n'échouera (à l'exécution) que si elle tente d'accéder à des attributs non définis dans les paramètres transmis.

Donc, cette fonction simple:

def no_op(param1, param2):
    pass

... n'échouera pas quels que soient les deux arguments qui sont transmis.

Cependant, cette fonction:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... échouera au moment de l'exécution si param1 et param2 ne possèdent pas tous les deux d'attributs appelables nommés quack.

14
TM.

Beaucoup de langues ont des variables, qui sont d'un type spécifique et ont une valeur. Python n'a pas de variables; il a des objets et vous utilisez des noms pour faire référence à ces objets.

Dans d'autres langues, quand vous dites:

a = 1

alors une variable (généralement un entier) change son contenu à la valeur 1.

En python,

a = 1

signifie “utilise le nom a pour faire référence à l'objet 1”. Vous pouvez effectuer les opérations suivantes dans une session interactive Python:

>>> type(1)
<type 'int'>

La fonction type est appelée avec l'objet 1; Puisque chaque objet connaît son type, il est facile pour type de connaître ledit type et de le renvoyer.

De même, chaque fois que vous définissez une fonction

def funcname(param1, param2):

la fonction reçoit deux objets et les nomme param1 et param2, quel que soit leur type. Si vous voulez vous assurer que les objets reçus sont d'un type spécifique, codez votre fonction comme si ils appartenaient au (x) type (s) requis et interceptez les exceptions levées si elles ne le sont pas. Les exceptions levées sont généralement TypeError (vous avez utilisé une opération non valide) et AttributeError (vous avez essayé d'accéder à un membre inexistant (les méthodes sont également des membres)).

9
tzot

Python n'est pas fortement typé dans le sens d'une vérification de type statique ou à la compilation.

La plupart du code Python relève de ce que l'on appelle "Duck Typing" - par exemple, vous recherchez une méthode read sur un objet - vous ne vous en souciez pas si l'objet est un fichier sur un disque ou un socket, vous voulez simplement en lire N octets.

7
Mark Rushakoff

Comme Alex Martelli explique ,

La solution habituelle, privilégiée par Pythonic, est presque invariablement le "typage de canard": essayez d’utiliser l’argument comme si c’était du type souhaité, faites-le dans une instruction try/except en prenant en compte toutes les exceptions qui pourraient se produire si l’argument n’était pas réellement de ce type (ou tout autre type imitant bien canard ;-), et dans la clause except, essayez autre chose (en utilisant l'argument "comme si" il était d'un autre type).

Lire le reste de son post pour des informations utiles.

5
Nick Presta

Python ne se soucie pas de ce que vous transmettez à ses fonctions. Lorsque vous appelez my_func(a,b), les variables param1 et param2 contiendront alors les valeurs de a et b. Python ne sait pas que vous appelez la fonction avec les types appropriés et attend du programmeur qu'il s'en charge. Si votre fonction sera appelée avec différents types de paramètres, vous pouvez envelopper le code en y accédant avec des blocs try/except et évaluer les paramètres comme vous le souhaitez.

3
Kyle

Il y a une exception notoire à la dactylographie qui mérite d'être mentionnée sur cette page.

Lorsque la fonction str appelle la méthode __str__ class, elle vérifie subtilement son type:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type Tuple)

Comme si Guido nous indiquait quelle exception un programme devrait-il déclencher s’il rencontre un type inattendu.

2
Antony Hatchkins

Vous ne spécifiez jamais le type; Python a le concept de frappe de canard ; Fondamentalement, le code qui traite les paramètres émet certaines hypothèses - peut-être en appelant certaines méthodes qu'un paramètre est censé implémenter. Si le paramètre est de type incorrect, une exception sera levée.

En général, il appartient à votre code de s’assurer que vous transmettez des objets du type approprié. Aucun compilateur ne l’impose à l’avance.

2
Justin Ethier

Dans Python, tout a un type. Une fonction Python fera tout ce qui lui sera demandé si le type d'arguments le prend en charge.

Exemple: foo ajoutera tout ce qui peut être __add__ed;) sans se soucier de son type. Cela signifie donc que, pour éviter les échecs, vous ne devez fournir que les éléments prenant en charge l’addition.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail
1
Pratik Deoghare

Je n'ai pas vu cela mentionné dans d'autres réponses, alors je vais l'ajouter à la casserole.

Comme d'autres l'ont dit, Python n'impose pas le type aux paramètres de fonction ou de méthode. Il est supposé que vous savez ce que vous faites et que si vous avez vraiment besoin de savoir quel type de quelque chose a été transféré, vous le vérifierez et déciderez quoi faire pour vous-même.

L'un des principaux outils pour ce faire est la fonction isinstance ().

Par exemple, si j’écris une méthode qui attend d’obtenir des données textuelles binaires brutes, plutôt que les chaînes codées utf-8 normales, je pourrais vérifier le type des paramètres lors de l’arrivée et s’adapter à ce que je trouve ou lever un exception à refuser.

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Python fournit également toutes sortes d’outils pour creuser des objets. Si vous êtes courageux, vous pouvez même utiliser importlib pour créer vos propres objets de classes arbitraires, à la volée. Je l'ai fait pour recréer des objets à partir de données JSON. Une telle chose serait un cauchemar dans un langage statique comme C++.

1
Dread Quixadhal

Pour utiliser efficacement le module de frappe (nouveauté dans Python 3.5), incluez tout (*).

from typing import *

Et vous serez prêt à utiliser:

List, Tuple, Set, Map - for list, Tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

Cependant, vous pouvez toujours utiliser des noms de type comme int, list, dict, ...

0
prosti