Types de paramètres de fonction en Python

sauf erreur de ma part, créer une fonction en Python fonctionne comme ceci:

def my_func(param1, param2):
    # stuff

cependant, vous ne donnez pas réellement les types de ces paramètres. Aussi, si je me souviens bien, Python est un langage fortement typé, en tant que tel, il semble que Python ne devrait pas vous laisser passer un paramètre d'un type différent de celui que le créateur de fonction attendait. Cependant, comment Python sait-il que l'utilisateur de la fonction passe dans les types appropriés? Le programme mourir juste si c'est le mauvais type, en supposant que la fonction utilise le paramètre? Avez-vous de préciser le type?

172
demandé sur efotinis 2010-03-22 05:17:20

11 réponses

Python est fortement dactylographié parce que chaque objet a un type, chaque objet sait son type, il est impossible d'utiliser accidentellement ou délibérément un objet d'un type "comme si" il était un objet d'un différent type, et toutes les opérations élémentaires sur l'objet sont déléguées à son type.

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

un nom en Python peut parfaitement se référer à différents objets à différents moments (comme dans la plupart des langages de programmation, mais pas tous) -- et il n'y a aucune contrainte sur le nom tel que, s'il a une fois en référence à un objet de type X, il est donc forevermore contraint de se référer uniquement à d'autres objets de type X. contraintes sur noms ne font pas partie du concept de "typage fort", bien que certains passionnés de statique typage (où les noms do obtenir contraint, et dans un statique, AKA compilation-temps, mode, aussi) faire un usage abusif du terme de cette façon.

118
répondu Alex Martelli 2016-04-21 17:38:58

les autres réponses ont fait un bon travail pour expliquer la dactylographie du canard et la réponse simple par tzot :

Python n'a pas de variables, comme les autres langues 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 (quand la question a été posée pour la première fois)), à savoir la mise en œuvre de PEP 3107 (mis en œuvre en Python 3). Vous pouvez maintenant spécifier le type d'un paramètre et le type de le 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 aussi retourner 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, il peut être un peu confus quant à ce que la liste doit contenir. Nous voulons aussi que la valeur par défaut de index soit 0. Pour résoudre ceci vous pouvez choisir d'écrire pick comme ceci à la place:

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

notez que nous avons maintenant mis dans une chaîne de caractères comme le type de l , qui est syntaxiquement autorisé, mais il n'est pas bon pour l'analyse programmatique (dont nous reviendrons plus tard).

il est il est important de noter que Python ne relèvera pas un TypeError si vous passez un flotteur dans index , la raison en est l'un des points principaux de la philosophie de conception de Python: "nous sommes tous des adultes consentants ici" , ce qui signifie que vous êtes censé être conscient de ce que vous pouvez passer à une fonction et de ce que vous ne pouvez pas. Si vous voulez vraiment écrire du code qui lance des typographes, vous pouvez utiliser la fonction isinstance pour vérifier que l'argument passé est du bon type ou un sous-classe, comme ceci:

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

plus sur les raisons pour lesquelles vous devriez rarement faire ceci et ce que vous devriez faire à la place est parlé 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 ajustable que vous pouvez lire sur ici .


Type annotation a reçu beaucoup plus d'attention en Python 3.5 avec l'introduction de PEP 484 qui introduit un module standard pour les indices de type.

cette syntaxe provient de l'outil de vérification de type statique optionnel mypy ( GitHub ) qui est en développement (et Pep 484 conforme).

avec le module de dactylographie est livré avec une collection assez complète de conseils de type, y compris:

  • List , Tuple , Set , Map - pour list , tuple , set et map respectivement.
  • Iterable - utile pour les générateurs.
  • Any - quand ça pourrait être n'importe quoi.
  • Union - quand il pourrait être n'importe quoi dans un ensemble spécifié de types, par opposition à Any .
  • Optional - quand il pourrait être aucun. Abréviation de Union[T, None] .
  • TypeVar - utilisé avec des génériques.
  • Callable - utilisé principalement pour des fonctions, mais pourrait être utilisé pour d'autres appelants.

cette liste représente les types d'indices les plus courants, mais elle est loin d'être exhaustive. Une liste complète est disponible dans la documentation pour le module de frappe .

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

from typing import List

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

une caractéristique puissante est le Callable qui vous permet de taper des méthodes annotées qui prennent une fonction comme 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 au lieu de Any , mais cela a été laissé comme un exercice pour le lecteur puisque je crois que j'ai déjà rempli ma réponse avec trop d'informations sur les nouvelles fonctionnalités merveilleuses activées par la suggestion de type.


précédemment lorsqu'un code Python documenté avec par exemple Sphinx certaines des fonctionnalités ci-dessus pourraient ê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 voir, cela prend un certain nombre de supplémentaires lignes (le nombre exact dépend de la façon dont vous voulez être explicite et comment vous formatez votre docstring). Mais il devrait maintenant être clair pour vous comment PEP 3107 fournit une alternative qui est dans beaucoup (tous?) façons de qualité 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 types de conseils / annotations qui peuvent être utilisés de telle manière qu'il est sans ambiguïté et précis encore flexible, ce qui permet une combinaison puissante.

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


un exemple de code Python qui utilise fortement l'indication de type peut être trouvé ici .

369
répondu erb 2018-02-08 07:59:28

vous ne spécifiez pas de type. La méthode n'échouera (à l'exécution) que si elle essaie d'accéder à des attributs qui ne sont pas définis sur les paramètres passés.

ainsi cette fonction simple:

def no_op(param1, param2):
    pass

... n'échouera pas, peu importe ce que deux args sont passés.

cependant, cette fonction:

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

... échouera à l'exécution si param1 et param2 n'ont pas tous les deux attributs appelables nommés quack .

13
répondu TM. 2010-03-22 02:22:42

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 vous référer à ces objets.

Dans d'autres langues, quand vous dites:

a = 1

puis une variable (généralement entière) change son contenu à la valeur 1.

En Python,

a = 1

signifie "utiliser le nom un faire référence à l'objet 1 ". Vous pouvez faire ce qui suit dans une session Python interactive:

>>> 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 trouver ledit type et de le retourner.

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 , quels que soient leurs types. Si vous voulez vous assurer que les objets reçus sont d'un type spécifique, coder votre fonction comme s'ils étaient du(DES) type (S) nécessaire (s) et attraper les exceptions qui sont lancées si elles ne le sont pas. Les exceptions lancées sont typiquement TypeError (vous avez utilisé une opération invalide) et AttributeError (vous avez essayé d'accéder à un membre inexistant (les méthodes sont aussi des membres) ).

7
répondu tzot 2010-04-02 22:03:01

Python n'est pas fortement typé au sens de vérification de type statique ou de compilation.

la plupart des codes Python relèvent de ce qu'on appelle " Duck Typing " -- par exemple, vous recherchez une méthode read sur un objet -- vous ne vous souciez pas si l'objet est un fichier sur le disque ou une socket, vous voulez juste lire N octets à partir de celui-ci.

5
répondu Mark Rushakoff 2010-03-22 02:20:17

As Alex Martelli explique ,

la solution normale, pythonique, préférée est presque invariablement "duck typing": essayez d'utiliser l'argument comme s'il s'agissait d'un certain type désiré, faites-le dans un try / except statement attraper toutes les exceptions qui pourraient surgir si l'argument n'était pas en fait de ce type (ou tout autre type joliment duck-imitant;-), et dans la clause d'exception, essayer quelque chose d'autre (en utilisant l'argument "comme si" il était de un autre type).

Lire le reste de son post pour obtenir des informations utiles.

5
répondu Nick Presta 2017-05-23 11:55:10

Python ne se soucie pas de ce que vous transmettez à ses fonctions. Lorsque vous appelez my_func(a,b) , les variables param1 et param2 tiendront alors les valeurs de a et B. Python ne sait pas que vous appelez la fonction avec les types appropriés, et s'attend à ce que le programmeur s'en occupe. Si votre fonction sera appelée avec différents types de paramètres, vous pouvez envelopper le code d'y accéder avec try/except blocks et évaluer les paramètres de la façon que vous voulez.

3
répondu Kyle 2017-01-27 14:09:44

vous ne spécifiez jamais le type; Python a le concept de Duck typing ; fondamentalement le code qui traite les paramètres fera certaines hypothèses à leur sujet - peut-être en appelant certaines méthodes qu'un paramètre est censé mettre en œuvre. Si le paramètre est du mauvais type, alors une exception sera lancée.

en général, c'est à votre code de s'assurer que vous faites circuler des objets du type approprié - il n'y a pas de compilateur pour appliquer cela à l'avance.

2
répondu Justin Ethier 2010-03-22 02:22:42

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

quand str fonction appelle __str__ méthode de classe il 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 soulever s'il rencontre un type inattendu.

2
répondu Antony Hatchkins 2015-07-31 11:30:15

en Python tout a un type. Une fonction Python fera tout ce qu'on lui demande de faire si le type d'arguments le supporte.

exemple: foo ajoutera tout ce qui peut être __add__ ed ;) sans se soucier beaucoup de son type. Donc, cela signifie, pour éviter l'échec, vous devez fournir seulement les choses qui soutiennent l'ajout.

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
répondu Pratik Deoghare 2017-12-06 21:32:59

Je n'ai pas vu cela mentionné dans d'autres réponses, donc je vais ajouter ceci au pot.

comme d'autres L'ont dit, Python n'impose pas le type sur les 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 connaître le type de quelque chose qui a été passé, vous le vérifier et décider 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 s'attend à obtenir des données texte binaires brutes, plutôt que les chaînes encodées utf-8 normales, je pourrais vérifier le type des paramètres sur le chemin et soit s'adapter à ce que je trouve, ou soulever une 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 dans les objets. Si vous êtes courageux, vous pouvez même utiliser importlib pour créer vos propres objets de classes arbitraires, à la volée. J'ai fait ça pour recréer des objets à partir de données JSON. Une telle chose serait un cauchemar dans un langage statique comme en C++.

1
répondu Dread Quixadhal 2017-12-06 21:40:40