15 Jan 2008

(Ugly) Python type checking

I like Python because of the explicitness of the syntax

def add(a,b):
return a + b


explicitness is good as it really leads to code that is understandable at one glance, but quick, tell me two types that the above function will work on ?

int’s and strings




>>> add(1,2)
3
>>> add(‘hello ‘,‘world’)
hello world



the above function works on both integers and strings only because both provide the special method add which gets called for the + operator.

So does this lead to implicitness ? Not really, because you should know (from programming) that you can add integers together and concatenate strings together, Python just makes this general across types.

If you wanted to say, restrict the types of the arguments to our add function above, you could do something like the following


def add(a,b):
if type(a)==type(str) and type(b)==type(str):
return a + b


Type checking is kind of ambiguous to me in a dynamic language. If i want to restrict the the ability of a function to only work with certain types or i don’t know the types of the object im passing to a function then i have design issues (or no design at all).

You could rewrite the above function to do the type checking using a decorator.

EDIT: i didn’t know if the following decorator was written by the original creators or not, but as i was pointed out it wasn’t here is the original link

Python Cookbook Recipe

def require(arg_name, allowed_types):
def make_wrapper(f):
if hasattr<
(f, "wrapped_args"):
wrapped_args = getattr(f, "wrapped_args")
else:
code = f.func_code
wrapped_args = list(code.co_varnames
[:code.co_argcount])

try:
arg_index = wrapped_args.index(arg_name)
except ValueError:
raise NameError, arg_name

def wrapper(
args, kwargs):
if len(args) > arg_index:
arg = args[arg_index]
else:
arg = kwargs[arg_name]

if not isinstance(arg, allowed_types):
type_list = " or ".join("'"
+ str(allowed_type.name) +
"'" for allowed_type in allowed_types)
raise TypeError, "Expected argument '%s' </span>
to be of type %s but it was of type '%s'." <br /> % (arg_name, type_list,
arg.class.name)

return f(*args,
kwargs)

wrapper.wrapped_args = wrapped_args
return wrapper

return make_wrapper

@require('a',str)
@require('b',str)
def add(a,b):
return a+b




>>>add(‘hello ‘,‘world’)
hello world
>>>add(‘hello’,1)
Traceback (most recent call last):
File “snippet3.py”, line 38, in
print add(‘hello’,2)
File “snippet3.py”, line 24, in wrapper
return f(*args, **kwargs)
File “snippet3.py”, line 22, in wrapper
raise TypeError, “Expected argument ‘%s’ to be of type %s
but it was of type ‘%s’.” % (arg_name,
type_list, arg.class.name)
TypeError: Expected argument ‘b’ to be of type
‘str’ but it was of type ‘int’.



above is code that does type checking on input arguments. I may be wrong and there may be use cases where you need to check the type of an object but the point is you should design your program so that you know all the involved types or use a language that has compile time type-checking.

Note: The above type checking is in a Django application that is live and it has users and i didn’t write that decorator.