The two decorators checkparams and checkreturn allow you to check (at execution time) that function parameters or a function return value are the right type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
""" File checkparams.py Version 2.0 Written by ll.snark Type Checking in Python using decorators Use the decorator checkparams to check (only unnamed) parameters type Use the decorator checkreturn to check a return value type : @checkreturn(int) @checkparams(int,int) def ajoute(a,b) : if a<0 : return "No negative numbers" return a+b Then Try : ajoute(5,6) ajoute(5,"Argh") ajoute(-4,6) Version 2.0 ( with help from ActiveState members... : -better type checking -removed unused lines -used functools.wraps to copy __name__ and __doc__ ) Note that the preceding decorated function checks if the parameters are type int or a subtype of int (this is a different behaviour from version 1, but I think it's better) Consequently, you can check only the second parameter, for instance, writing : @checkparams(object,int) def ajoute(a,c) : ... Indeed, type of parameter a will always be a subclass of object. """ import functools def checkparams(*params) : def decorator(fonc) : @functools.wraps(fonc) def foncm(*pnn,**pn) : for i,par in enumerate(zip(pnn,params)) : if not isinstance(par,par) : raise TypeError(foncm.__name__+" Param "+str(i)+" ("+str(par)+") is not "+str(par)) r=fonc(*pnn,**pn) return r return foncm return decorator def checkreturn(tor) : def decorator(fonc) : @functools.wraps(fonc) def foncm(*pnn,**pn) : r=fonc(*pnn,**pn) if not isinstance(r,tor) : print(type(r)) raise TypeError(foncm.__name__+" : Return value ("+str(r)+") is not "+str(tor)) return r return foncm return decorator if __name__=='__main__' : @checkreturn(int) @checkparams(int,int) def ajoute(a,b) : """ Just a sample function to test decorators. """ if a<0 : return "No negative numbers" return a+b print(ajoute(5,6)) print(ajoute(0,"foo")) print(ajoute(-3,4))
I don't know if this kind of type checking decorators are well known by all of you. I wrote this to better understand decorators. Note that if type checking fails, a TypeError Exception is raised, giving you the name of the function you wrote, not the name of the decorated function. If you think I do this the wrong way (copying __name__ and __doc__), please, tell me.
I also wonder if this : type(5)==int is the right way to test parameters type...
I updated this recipe taking Matthew remarks into account.
I've been messing around with a few things like this, and I have a few things that may help.
I'd check to see if it's an instance of that class instead of comparing the type directly.
Also, checkout the functools.wraps method. It does the __name__ and __doc__ replacement for you.
And I guess lastly, I have a personal pet pieve against enumerating zip results as I can't unpack them cleanly. So I convert:
But again, that's just a pet pieve of mine. You could also just unpack them yourself:
Anyway, this is great! And like I said, I've been messing around with this for quite some time trying to validate user inputs in LOTS of places. This methodology is pretty clean, and I'll likely use something very much like it.
This is the best kind of optional type-checking I've ever seen for Python! Great work! Way better than Python's recent functional annotation style.
Great stuff. I'm not sure but i think it looks cleaner than what I did when I made something similar a few months ago.
I wondered- why do you prefer to use regular and not keyword args (or allowing both) when defining the types for a func? i.e. Why
@checkparams(a= int, b = int)?
About Matthew's pet peeve - I sympathize; what I usually do is something like this
Which is not as bad as
par, pari guess.
I think it would be useful -- and easy -- to have
checkparams()also check the number of parameters passed.
It might also be good to make
checkreturn()be able to check the types of a tuple of return values since that's a fairly common Python idiom.
I started writing a response regarding a few details. One was to pass key-values allowing for specific attributes to be type-checked while ignoring others i.e. 3rd param must be an integer:
Another was to draw your attention to some of the Python introspection tools, particularly inspect, which has the ability to return the calling arguments, and much more.
This would allow the prev. example to become more specific to an attribute name, allowing the decorator to handle indexing the arguments, reducing potential errors. Plus, I was going to suggest multi-type definitions. So combined the usage could look like this:
You may also have noted the name-space resolution (no 'int_') in this example too.
But life happened before I could finish some proof-of-concept and hit send... two days later I find a full package on pypi that relates to this recipe.
Please have a look at "typesafe" at pypi.python.org. It actually implements the above suggestions in two forms: strict and lenient.
There is another package called "typecheck" but I've not looked into it.