web-dev-qa-db-fra.com

Vérification de type des arguments Python

Parfois, la vérification des arguments dans Python est nécessaire. Par exemple, j'ai une fonction qui accepte l'adresse d'un autre nœud du réseau comme adresse de chaîne brute ou la classe Node = qui encapsule les informations de l'autre nœud.

J'utilise la fonction type () comme dans:

    if type(n) == type(Node):
        do this
    Elif type(n) == type(str)
        do this

Est-ce une bonne façon de procéder?

Mise à jour 1: Python 3 a une annotation pour les paramètres de fonction. Ceux-ci peuvent être utilisés pour les vérifications de type à l'aide de l'outil: - http://mypy-lang.org/

38
Xolve

Utilisez isinstance(). Échantillon:

if isinstance(n, unicode):
    # do this
Elif isinstance(n, Node):
    # do that
...
109
Johannes Weiss
>>> isinstance('a', str)
True
>>> isinstance(n, Node)
True
13
SilentGhost

Non, la vérification des arguments dans Python n'est pas nécessaire. C'est jamais nécessaire.

Si votre code accepte des adresses en tant que chaîne brute ou en tant qu'objet Node, votre conception est rompue.

Cela vient du fait que si vous ne connaissez pas déjà le type d'un objet dans votre propre programme, vous faites déjà quelque chose de mal.

La vérification typographique nuit à la réutilisation du code et réduit les performances. Avoir une fonction qui exécute différentes choses selon le type d'objet transmis est sujet aux bogues et a un comportement plus difficile à comprendre et à maintenir.

Vous disposez des options d'assainissement suivantes:

  1. Créez un constructeur d'objet Node qui accepte les chaînes brutes, ou une fonction qui convertit les chaînes en objets Node. Faites en sorte que votre fonction suppose que l'argument passé est un objet Node. De cette façon, si vous devez passer une chaîne à la fonction, vous n'avez qu'à:

    myfunction(Node(some_string))
    

    C'est votre meilleure option, elle est propre, facile à comprendre et à entretenir. Quiconque lit le code comprend immédiatement ce qui se passe, et vous n'avez pas à vérifier la frappe.

  2. Créez deux fonctions, une qui accepte les objets Node et une qui accepte les chaînes brutes. Vous pouvez faire un appel à l'autre en interne, de la manière la plus pratique (myfunction_str Peut créer un objet Node et appeler myfunction_node, Ou l'inverse).

  3. Assurez-vous que les objets Node ont une méthode __str__ Et à l'intérieur de votre fonction, appelez str() sur l'argument reçu. De cette façon, vous obtenez toujours une chaîne par coercition.

Dans tous les cas, ne pas cocher la case. C'est complètement inutile et n'a que des inconvénients. Refactorisez votre code à la place d'une manière que vous n'avez pas besoin de vérifier. Vous obtenez uniquement des avantages à le faire, à court et à long terme.

8
nosklo

On dirait que vous recherchez une "fonction générique" - qui se comporte différemment en fonction des arguments donnés. C'est un peu comme comment vous obtiendrez une fonction différente lorsque vous appelez une méthode sur un objet différent, mais plutôt que d'utiliser simplement le premier argument (l'objet/self) pour rechercher la fonction, vous utilisez plutôt tous les arguments.

Turbogears utilise quelque chose comme ça pour décider comment convertir des objets en JSON - si je me souviens bien.

Il y a n article d'IBM sur l'utilisation du package dispatcher pour ce genre de chose:

De cet article:

import dispatch
@dispatch.generic()
def doIt(foo, other):
    "Base generic function of 'doIt()'"
@doIt.when("isinstance(foo,int) and isinstance(other,str)")
def doIt(foo, other):
    print "foo is an unrestricted int |", foo, other
@doIt.when("isinstance(foo,str) and isinstance(other,int)")
def doIt(foo, other):
    print "foo is str, other an int |", foo, other
@doIt.when("isinstance(foo,int) and 3<=foo<=17 and isinstance(other,str)")
def doIt(foo, other):
    print "foo is between 3 and 17 |", foo, other
@doIt.when("isinstance(foo,int) and 0<=foo<=1000 and isinstance(other,str)")
def doIt(foo, other):
    print "foo is between 0 and 1000 |", foo, other
6
John Montgomery

Vous pouvez également utiliser une tentative de capture pour taper check si nécessaire:

def my_function(this_node):
    try:
        # call a method/attribute for the Node object
        if this_node.address:
             # more code here
             pass
    except AttributeError, e:
        # either this is not a Node or maybe it's a string, 
        # so behavior accordingly
        pass

Vous pouvez voir un exemple de cela dans Beginning Python dans la seconde sur les générateurs (page 197 dans mon édition) et je crois dans le Python Cookbook. Souvent, attraper un AttributeError ou TypeError est plus simple et apparemment plus rapide. En outre, cela peut fonctionner mieux de cette manière car vous n'êtes alors pas lié à un arbre d'héritage particulier (par exemple, votre objet peut être un Node ou il peut s'agir d'un autre objet qui a le même comportement qu'un Node).

6
Rob