2009-12-16 4 views
1

Je suis encore assez nouveau à Python et j'essaie de m'habituer à son typage dynamique. Parfois, j'ai une fonction ou une classe qui attend un paramètre d'un certain type, mais pourrait obtenir une valeur d'un autre type qui lui est coercible. Par exemple, il peut s'attendre à un float mais recevoir un int ou un nombre décimal. Ou il peut s'attendre à une chaîne, mais reçoit à la place un objet qui définit la méthode spéciale __str__.Meilleur endroit pour contraindre/convertir au bon type en Python

Quelle est la meilleure pratique pour contraindre l'argument au bon type (et la raison pour cela)? Est-ce que je le fais dans la fonction/classe ou dans l'appelant? Si je suis dans l'appelant, est-ce que je le vérifie également dans la fonction? Par exemple.

Alternative 1:

def myfunc(takes_float): 
    myval = float(takes_float) 

myfunc(5) 

Alternative 2:

def myfunc(takes_float): 
    myval = takes_float 

myfunc(float(5)) 

Alternative 3:

def myfunc(takes_float): 
    assert isinstance(takes_float, float) 
    myval = takes_float 

myfunc(float(5)) 

Je l'ai déjà ils lisent this answer et this one et ils disent que vérifier les types en Python est "mauvais", mais je ne veux pas perdre de temps à rechercher des bogues très simples qui seraient immédiatement détectés par le compilateur dans un langage statique.

+0

N'utilisez pas 'assert' comme ça. Les assertions sont destinées à documenter des conditions qui ne peuvent pas se produire lors de l'exécution, et non à valider une entrée. –

+0

Oui, impossible d'arriver à moins qu'il y ait un bug. En alternative 3, où l'appelant doit convertir la valeur, ce serait un bug. – EMP

Répondre

8

Vous "contraindre" (peut-être - ce pourrait être un noop) quand il est indispensable pour vous de le faire, et non plus tôt. Par exemple, supposons que vous avez une fonction qui prend un flotteur et renvoie la somme de ses sinus et cosinus:

import math 
def spc(x): 
    math.sin(x) + math.cos(x) 

Où devriez-vous « forcer » x flotter? Réponse: nulle part du tout - sin et cos faire ce travail pour vous, par exemple .:

>>> spc(decimal.Decimal('1.9')) 
0.62301052082391117 

Alors, quand il est indispensable pour contraindre (le plus tard possible)? Par exemple, si vous souhaitez appeler des méthodes de chaîne sur un argument, vous devez vous assurer qu'il s'agit d'une chaîne - en essayant d'appeler par exemple. .lower sur une non-chaîne ne fonctionnera pas, len peut fonctionner mais faire quelque chose de différent que vous attendez si l'argument est par exemple. une liste (vous donne le nombre d'éléments dans la liste, pas le nombre de caractères que sa représentation en chaîne prendra), et ainsi de suite. En ce qui concerne les erreurs de rattrapage - pensez unit tests - les tests unitaires semi-sécables captureront toutes les erreurs de typage statique, et quelques-unes. Mais, c'est un sujet différent.

+0

s/stating/static/ –

+0

@Andrey, ouais, ça sonne mieux - laissez-moi réparer le A, merci. –

2

Cela dépend vraiment. Pourquoi avez-vous besoin un float? Un int casserait-il la fonction? Si oui, pourquoi?

Si vous avez besoin du paramètre pour soutenir une fonction/propriété qu'un float a, mais un int ne pas que vous devriez vérifier cette fonction/propriété, ne que le paramètre se trouve être un float. Vérifiez que l'objet peut faire ce que vous avez besoin de faire, et non qu'il s'agit d'un type particulier avec lequel vous êtes familier.

Qui sait, peut-être que quelqu'un trouvera un problème majeur avec l'implémentation de Python de float et de créer une bibliothèque notbrokenfloat. Il pourrait supporter tout ce qu'un flotteur fait en corrigeant un bug exotique, mais ses objets ne seraient pas de type float. Le lancer manuellement à un float pourrait supprimer tous les avantages de cette nouvelle classe astucieuse (ou pourrait casser purement et simplement).

Oui, c'est un exemple improbable, mais je pense que c'est le bon état d'esprit à adopter lorsqu'on travaille avec un langage dynamiquement typé.

0

Il y a exactement une fois où le nombre entier contre le flotteur sera un problème. C'est la seule fois où vous trouverez un bogue "simple" qui est bizarre et un défi à déboguer.

Division.

Tout le reste fait la conversion dont vous avez besoin quand vous en avez besoin.

Si vous utilisez Python 2.x et que vous jetez les opérateurs / sans réfléchir, vous pouvez - dans certaines circonstances courantes - finir par faire la mauvaise chose.

Vous avez plusieurs choix.

  1. from __future__ import division vous donnera la sémantique de Python 3 pour la division.

  2. Exécuter avec l'option -Qnew à tout moment pour obtenir la nouvelle sémantique de division.

  3. Utilisez float opérations près /.

Division est le seul endroit où le type peut importer. C'est la seule fois que les nombres entiers se comportent différemment des flottants d'une manière qui affecte silencieusement vos résultats.

Tous les autres problèmes d'incompatibilité de type échoueront de façon spectaculaire avec une exception TypeError. Tous les autres. Vous ne perdrez pas de temps à déboguer. Vous saurez immédiatement ce qui ne va pas.


Pour être plus précis.

Il n'y a pas de débogage de "attend une chaîne mais n'a pas de chaîne". Cela va planter immédiatement avec un retraçage. Pas de confusion. Pas de temps perdu à penser. Si une fonction attend une chaîne, l'appelant doit fournir la chaîne - c'est la règle.

L'option 2 ci-dessus est utilisée RAREMENT pour corriger le problème où vous avez une fonction qui attend une chaîne ET vous avez confondu et oublié de fournir une chaîne. Cette erreur se produit RAREMENT et cela conduit à une exception de type immédiat.

+0

Merci, bon de savoir les options pour cela - mais la chose int/float était juste un exemple. Il existe d'autres cas de problème. – EMP

+0

@Evgeny: Vraiment?Pouvez-vous en fournir? Le seul qui prête à confusion est int/float. AFAIK, Tous les autres meurent avec des exceptions évidentes. Veuillez fournir un exemple d'opération qui ne meurt pas avec une exception. –

+0

Un autre exemple est dans mon article d'origine: la méthode peut s'attendre à une chaîne, mais reçoit un objet qui définit la méthode spéciale '__str__'. Quelque part le long de la ligne, je dois appeler 'str()', mais où? – EMP

Questions connexes