Sign in to follow this  
mikeman

Some type checking in Python

Recommended Posts

mikeman    2942
I've been playing around with the idea of adding some run-time typechecking in Python, just for debugging purposes. That is, type-checking is only on during the development of the program, and probably disabled when it's about to be released for the users. I've come up with a simple module that handles most of this, so I thought I shared. First things first, this is the module: safe.py
import types

#just for some convenience...
number=(int,long,float)
iterable=(list,tuple)

def safe(*sig):
    def _f(f):
        func=partial(safecall,f,sig)
        func.__doc__=f.__doc__
        func.sig=sig
        return func
    return _f

#Uncomment the below if you want to disable type-checking entirely(even if the func is decorated as @safe)
"""
def safe(*sig):
    def _f(f):
        return f
    return _f
"""

def partial(f,*bind_args):
    def _f(*args):
        return f(*(bind_args+args))
    return _f   
             
def safecall(f,sig,*args):
    i=0
    l=len(sig)
    #check arguments
    for (arg,typ) in zip(args,sig[0:l-1]):
        if not isinstance(arg,typ):
            raise TypeError,"Incorrect type of arg"+str(i)+":expected "+str(typ)+" got "+str(type(arg))+" instead"
        i=i+1
    result=f(*args)
    #check result    
    if not isinstance(result,sig[l-1]):
        raise TypeError,"Wrong result type: expected"+str(sig[l-1])+" got "+str(type(result))+" instead"
    return result



The idea is that you use the decorator @safe to give a function a signature. So, if the function accepts two ints and returns a bool, the decorator will be: @safe(int,int,bool) Instead of one type, you can define a tuple of types the arguments will be checked against. To make the above function work for any kind of number(int,long,float), you just do: number=(int,long,float) @safe(number,number,bool) Of course, inheritence rules are applied during type checking.Also,if you don't want to type-check a specific argument, and want it to accept any type, you can use the generic object type. What's more, the function/method gets a "sig" member that contains the "signature" as defined in the decorator. So you can just do "print foo.sig" for a @safe "foo" function to inspect the types of arguments and result it expects. The below code demonstrates in more detail the use of the module(it supports methods too).
from safe import *
from types import *

@safe(number,number,number)
def add_numbers(x,y):
    return x+y

@safe(str,str,str)
def concat(s1,s2):
    return s1+s2

class Foo(object):
    @safe(object,str,number,iterable,NoneType)
    def __init__(self,name,x,lst):
        self.x=x
        self.name=name
        self.lst=lst

#Print some signatures...
print "add_numbers:",add_numbers.sig
print "concat:",concat.sig
print "Foo.__init__:",Foo.__init__.sig
        
#All these are legal:
foo=Foo("Foo",42,[1,2,3])
print add_numbers(2,3)
print concat("Hello ","world")

#All these produce exceptions:        
#print add_numbers(2,"Hello")
#print concat(1,2)
#foo=Foo("Foo","42",1)



I hope some at least Pythonistas will find it useful to make the debugging sessions somewhat easier. Also, don't be afraid to leave the comment if you think this idea and module are just awful ;)

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Ah... I tried to link to this:
http://wiki.python.org/moin/PythonDecoratorLibrary#head-308f2b3507ca91800def19d813348f78db34303e

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this