web-dev-qa-db-fra.com

Meilleur moyen de vérifier les arguments des fonctions en Python

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
44
carmellose

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.

52

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))
16
rlms

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'
10
Matthew Plourde

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
6
DominikStyp

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.

5
Games Brainiac

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].
2
smarie

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
2
Mats Kindahl

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)

1
Mahdi Ghelichi

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!

0
Paulo Bu

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

0
Mohit Thakur

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...
0
Jo So
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 ()

0
FallenAngel