I'd like to know your opinions on this minimal type-checking decorator (with @ annotations) to make type checking of a method while debugging like :

def typecheck(*tuples, **kwtuples):
    def decorator(func):
        def function_wrapper(*args, **kwargs):

            #print('tuples : ' , tuples)
            #print('kwtuples : ' , kwtuples)            
            #print('args : ' , args)            
            #print('kwargs : ' , kwargs)

            for index, tup in enumerate(tuples):
                arg = args[index]
                if not isinstance(arg, tup):
                    raise ValueError('in ' + str(func.__name__) + ' : wrong argument on position ,' + str(index) + ' :'  + str(arg) + ' must be of type :' + str(tup) + 'but is' + str(type(arg)) )

            for key, tup in kwtuples.items():
                arg = kwargs[key]
                if not isinstance(args[index], tup):
                    raise ValueError('in ' + str(func.__name__) + ' : wrong argument ' + str(key) + ' :'  + str(arg) + ' must be of type :' + str(tup) + 'but is' + str(type(arg)) )

        #print("arguments: ", *args, **kwargs)
            func(*args, **kwargs)
        return function_wrapper
    return decorator

@typecheck(str,(str, float))    
def foo2(x,y):
    print("Hi, foo has been called with ",str(x) ,y)


The benefit of it is :

  • Custom and detailed description about the wrong argument
  • Later, you can extend this for custom validation like some item is in some range or structural inspection of an argument.
  • Easy to write, apply and delete after testing (so it won't consume cpu time)
  • 2
    \$\begingroup\$ Have you considered somehow taking advantage of type hints, introduced in Python 3.5? (And if not, why?) \$\endgroup\$ May 27, 2019 at 18:39
  • 2
    \$\begingroup\$ In addition to @200_success other than using typing and mypy/pyre/pyright/pytype for static type enforcement, you can also use something like pytypes for run-time type enforcement. \$\endgroup\$
    – Peilonrayz
    May 27, 2019 at 18:43
  • \$\begingroup\$ those stuff do not custom value checking or structural checking, dont they? also i want it to be easy removed after testing \$\endgroup\$ May 27, 2019 at 19:08

1 Answer 1


To amplify @200_success’s comment:

Python 3.5 introduces type hints. Eg)

>>> def foo2(x:str, y:float) -> None:
...    print(f"Hi, foo has been called with {x} {y}")
>>> foo2("2", 3.4)
Hi, foo has been called with 2 3.4

These type hints (the x:str, and y:float) are not used by the standard Python interpreter, but they are recorded during the parsing of the code, so they are available to the program.

>>> foo2.__annotations__
{'return': None, 'x': <class 'str'>, 'y': <class 'float'>}

Directly accessing the foo2.__annotations__ is not that interesting, but accessing func.__annotations__ in the @typecheck decorator means you don’t have to provide the argument types to the decorator; the decorator can inspect the argument types directly. Thus you could decorate the function with simply @typecheck, instead of @typecheck(str, float).

For a variant str or float type argument, you could use the typing module to define your own STR_OR_FLOAT = TypeVar("STR_OR_FLOAT", str, float) type (or a better name if you have one), which you could decorate the argument with.

As a bonus, using type hints to provide argument type checking information — even for your do-it-yourself via a function decorator type check system — also gives you the following benefits:

  • IDE type hints, as you are writing code
  • Can be understood by standard documentation generators
  • Only consumes CPU time during parsing of the code; does not affect execution speed

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.