Je cherche un moyen efficace de vérifier les variables d'une fonction python. Par exemple, j'aimerais vérifier le type et la valeur des arguments. Y a-t-il un module pour cela? Ou devrais-je utiliser quelque chose comme des décorateurs, ou un langage spécifique?
def my_function(a, b, c):
"""an example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
La plupart des idiomes Pythonic consistent à indiquer clairement document ce à quoi la fonction s'attend, puis à essayer d'utiliser ce qui est transmis à votre fonction et à laisser les exceptions se propager ou simplement à intercepter les erreurs d'attribut et à générer un TypeError
à la place. La vérification du type doit être évitée autant que possible car elle va à l'encontre du dactylographie. Le test de valeur peut être OK - en fonction du contexte.
Le seul endroit où la validation a vraiment du sens est au point d’entrée du système ou du sous-système, tels que les formulaires Web, les arguments de ligne de commande, etc. Partout ailleurs, tant que vos fonctions sont bien documentées, il incombe à l’appelant de transmettre les arguments appropriés.
La vérification de type n’est généralement pas Pythonic. En Python, il est plus habituel d’utiliser le type de canard . Exemple:
Dans votre code, supposons que l'argument (dans votre exemple a
) marche comme une int
et des charlatans comme une int
. Par exemple:
def my_function(a):
return a + 7
Cela signifie que non seulement votre fonction fonctionne avec des entiers, mais également avec des flottants et que toute classe définie par l'utilisateur avec la méthode __add__
est définie, donc moins (parfois rien) ne doit être fait si vous, ou quelqu'un d'autre, souhaitez étendre votre fonction travailler avec autre chose. Cependant, dans certains cas, vous aurez peut-être besoin d'une int
. Vous pourrez alors faire quelque chose comme ceci:
def my_function(a):
b = int(a) + 7
c = (5, 6, 3, 123541)[b]
return c
et la fonction fonctionne toujours pour toute variable a
qui définit la méthode __int__
.
En réponse à vos autres questions, je pense que c'est mieux (comme d'autres réponses l'ont dit, faites ceci:
def my_function(a, b, c):
assert 0 < b < 10
assert c # A non-empty string has the Boolean value True
ou
def my_function(a, b, c):
if 0 < b < 10:
# Do stuff with b
else:
raise ValueError
if c:
# Do stuff with c
else:
raise ValueError
Quelques décorateurs de vérification de type que j'ai faits:
import inspect
def checkargs(function):
def _f(*arguments):
for index, argument in enumerate(inspect.getfullargspec(function)[0]):
if not isinstance(arguments[index], function.__annotations__[argument]):
raise TypeError("{} is not of type {}".format(arguments[index], function.__annotations__[argument]))
return function(*arguments)
_f.__doc__ = function.__doc__
return _f
def coerceargs(function):
def _f(*arguments):
new_arguments = []
for index, argument in enumerate(inspect.getfullargspec(function)[0]):
new_arguments.append(function.__annotations__[argument](arguments[index]))
return function(*new_arguments)
_f.__doc__ = function.__doc__
return _f
if __== "__main__":
@checkargs
def f(x: int, y: int):
"""
A doc string!
"""
return x, y
@coerceargs
def g(a: int, b: int):
"""
Another doc string!
"""
return a + b
print(f(1, 2))
try:
print(f(3, 4.0))
except TypeError as e:
print(e)
print(g(1, 2))
print(g(3, 4.0))
Une solution consiste à utiliser assert
:
def myFunction(a,b,c):
"This is an example function I'd like to check arguments of"
assert isinstance(a, int), 'a should be an int'
# or if you want to allow whole number floats: assert int(a) == a
assert b > 0 and b < 10, 'b should be betwen 0 and 10'
assert isinstance(c, str) and c, 'c should be a non-empty string'
Vous pouvez utiliser le type Enforcement accepter/retourner les décorateurs de PythonDecoratorLibrary C'est très simple et lisible:
@accepts(int, int, float)
def myfunc(i1, i2, i3):
pass
Il y a différentes façons de vérifier ce qu'est une variable en Python. Donc, pour en énumérer quelques-uns:
La fonction isinstance(obj, type)
prend votre variable, obj
et vous donne True
s'il s'agit du même type que la type
que vous avez indiquée.
issubclass(obj, class)
fonction qui prend une variable obj
et vous donne True
si obj
est une sous-classe de class
. Ainsi, par exemple, issubclass(Rabbit, Animal)
vous donnerait une valeur True
hasattr
est un autre exemple, démontré par cette fonction, super_len
:
def super_len(o):
if hasattr(o, '__len__'):
return len(o)
if hasattr(o, 'len'):
return o.len
if hasattr(o, 'fileno'):
try:
fileno = o.fileno()
except io.UnsupportedOperation:
pass
else:
return os.fstat(fileno).st_size
if hasattr(o, 'getvalue'):
# e.g. BytesIO, cStringIO.StringI
return len(o.getvalue())
hasattr
penche davantage vers le typage de canard, et quelque chose qui est généralement plus Pythonic mais ce terme est perçu comme prioritaire.
Comme remarque, les instructions assert
sont généralement utilisées dans les tests, sinon, utilisez simplement les instructions if/else
.
J'ai fait pas mal de recherches sur ce sujet récemment car je n'étais pas satisfait des nombreuses libraries que j'ai découvertes là-bas.
J'ai fini par développer une bibliothèque pour y remédier, elle s'appelle valid8 . Comme expliqué dans la documentation, il s’agit principalement de validation de valeur (bien qu’il soit livré avec des fonctions de validation de type simples) et vous pouvez l’associer à un vérificateur de type basé sur PEP484, tel que enforce ou pytypes .
Voici comment effectuer la validation avec valid8
seul (et mini_lambda
en réalité, pour définir la logique de validation - mais ce n'est pas obligatoire) dans votre cas:
# for type validation
from numbers import Integral
from valid8 import instance_of
# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len
@validate_arg('a', instance_of(Integral))
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', instance_of(str), Len(s) > 0)
def my_function(a: Integral, b, c: str):
"""an example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
# check that it works
my_function(0.2, 1, 'r') # InputValidationError for 'a' HasWrongType: Value should be an instance of <class 'numbers.Integral'>. Wrong value: [0.2].
my_function(0, 0, 'r') # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0) # InputValidationError for 'c' Successes: [] / Failures: {"instance_of_<class 'str'>": "HasWrongType: Value should be an instance of <class 'str'>. Wrong value: [0]", 'len(s) > 0': "TypeError: object of type 'int' has no len()"}.
my_function(0, 1, '') # InputValidationError for 'c' Successes: ["instance_of_<class 'str'>"] / Failures: {'len(s) > 0': 'False'}
Et voici le même exemple qui exploite les conseils de type PEP484 et délègue la vérification de type à enforce
:
# for type validation
from numbers import Integral
from enforce import runtime_validation, config
config(dict(mode='covariant')) # type validation will accept subclasses too
# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len
@runtime_validation
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', Len(s) > 0)
def my_function(a: Integral, b, c: str):
"""an example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
# check that it works
my_function(0.2, 1, 'r') # RuntimeTypeError 'a' was not of type <class 'numbers.Integral'>
my_function(0, 0, 'r') # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0) # RuntimeTypeError 'c' was not of type <class 'str'>
my_function(0, 1, '') # InputValidationError for 'c' [len(s) > 0] returned [False].
Normalement, vous faites quelque chose comme ça:
def myFunction(a,b,c):
if not isinstance(a, int):
raise TypeError("Expected int, got %s" % (type(a),))
if b <= 0 or b >= 10:
raise ValueError("Value %d out of range" % (b,))
if not c:
raise ValueError("String was empty")
# Rest of function
Ceci vérifie le type des arguments d'entrée lors de l'appel de la fonction:
def func(inp1:int=0,inp2:str="*"):
for item in func.__annotations__.keys():
assert isinstance(locals()[item],func.__annotations__[item])
return (something)
first=7
second="$"
print(func(first,second))
Vérifiez également avec second=9
(il doit donner une erreur d'assertion)
Si vous voulez faire la validation de plusieurs fonctions, vous pouvez ajouter la logique dans un décorateur comme ceci:
def deco(func):
def wrapper(a,b,c):
if not isinstance(a, int)\
or not isinstance(b, int)\
or not isinstance(c, str):
raise TypeError
if not 0 < b < 10:
raise ValueError
if c == '':
raise ValueError
return func(a,b,c)
return wrapper
et l'utiliser:
@deco
def foo(a,b,c):
print 'ok!'
J'espère que cela t'aides!
Ce n'est pas la solution pour vous, mais si vous souhaitez limiter les appels de fonction à certains types de paramètres spécifiques, vous devez utiliser le validateur de prototype PROATOR {The Python Function}. vous pouvez vous référer au lien suivant. https://github.com/mohit-thakur-721/proator
Si vous souhaitez vérifier **kwargs
, *args
ainsi que les arguments normaux en une fois, vous pouvez utiliser la fonction locals()
comme première instruction de votre définition de fonction pour obtenir un dictionnaire des arguments.
Utilisez ensuite type()
pour examiner les arguments, par exemple en parcourant le dict.
def myfunc(my, args, to, this, function, **kwargs):
d = locals()
assert(type(d.get('x')) == str)
for x in d:
if x != 'x':
assert(type(d[x]) == x
for x in ['a','b','c']:
assert(x in d)
whatever more...
def someFunc(a, b, c):
params = locals()
for _item in params:
print type(params[_item]), _item, params[_item]
Démo:
>> someFunc(1, 'asd', 1.0)
>> <type 'int'> a 1
>> <type 'float'> c 1.0
>> <type 'str'> b asd
en savoir plus sur locals ()