import inspect def typed(__return_type__ = None, __accept_subclasses__ = True, **argument_types): """ typed - run-time simulation of static typing in python A decorator that will throw a TypeError if the type of an argument value passed to the decorated function does not match the type defined by this decorator. Usage example below. Would throw a TypeError if the "string" argument is not of type str and if the type of the returned value is not int: @typed(int, string = str) def getStringLength(string): return len(string) """ def decorator(wrappee): # The decorator function that will be returned by typed() def wrapper(*args, **kwargs): """ The wrapper function that will be returned by decorator when the decorated function is called This is the actual function that will be run before the wrapped function runs """ def checkType(arg, expected_type, found_value, return_value = False): """ Raise an exception if the found value doesn't match the expected type Adapt exception text to return value/argument """ # Will be set to True if the found_value doesn't match the expected type bad_type = False # If the option accept subclasses is true, check if the found value is an instance of the expected # type and set bad_type = True if not if __accept_subclasses__: if not isinstance(found_value, expected_type): bad_type = True # If the option accept subclasses is false, check that the class of the found_value matches the # expected type exactly, and set bad_type = True if not else: if not found_value.__class__ == expected_type: #isinstance(found_value, expected_type): bad_type = True # Raise a TypeError if a bad type was found and adapt the text depending on if its a return type # or argument type that was bad if bad_type: if return_value: raise TypeError(u"Return value of %s() expected type %s but found type %s" % (wrappee.__name__, __return_type__, found_value.__class__)) else: raise TypeError(u"Argument %s of %s() expected type %s but found type %s" % (arg, wrappee.__name__, expected_type, found_value.__class__)) # Fetch an ordered list of the arguments defined for the wrapped function wrappee_args,_,_,_ = inspect.getargspec(wrappee) # Check all arguments defined by the wrapped function against the types defined for i, arg in enumerate(wrappee_args): if i >= len(args): break checkType(arg, argument_types[arg], args[i]) # Check all keywords arguments defined by the wrapped function against the types defined for arg in argument_types.keys(): if kwargs.has_key(arg): checkType(arg, argument_types[arg], kwargs[arg]) # Run the wrapped function result = wrappee(*args, **kwargs) # Check the return value against the type defined if __return_type__ is not None: checkType("", __return_type__, result, return_value = True) return result return wrapper return decorator ### # Usage example ### @typed(int, string = str) def getStringLength(string): return len(string) ##### # Simple tests below ##### def raisesTypeError(func, *args, **kwargs): try: func(*args, **kwargs) return False except TypeError: return True assert getStringLength("four") == 4 assert raisesTypeError(getStringLength, 4) @typed(int, string = str) def getStringLengthFloat(string): return float(len(string)) assert raisesTypeError(getStringLengthFloat, "four")