"""Example module without type checking.

Pros:
Clean, easily readable and extensible code that gets down to business 
fast. 
Pinpointed tracebacks with good error messages.
If I at some point decide that floats are also ok, I only need to 
update the docs and change the decorators to 
@method_argtypes((int, float)).

Cons:
With many args and allowed types, the type definitions on the decorator 
lines will be hard to correlate to the args that they refer to (probably 
not impossible to workaround though...?). 
We still don't catch errors of the type 
a = MyClass()
a.int_member = 'moo!"
a.process_data(1)
A decorator for each method everywhere? That can't be the purpose of 
python!? There has to be a better way?!

"""

def method_argtypes(*typedefs):
    """Rudimentary typechecker decorator generator.
    
    If you're really interested in this stuff, go check out Michele 
    Simionato's decorator module instead. It rocks. Google is your friend.

    IN:
    *typedefs: <type> or <tuple <type>>
        The allowed types for each arg to the method, self excluded.
        Will be used with isinstance(), so valid typedefs include
        int or (int, float).
    
    """
    def argchecker(fcn):
        import inspect
        names = inspect.getargspec(fcn)[0][1:]
        def check_args(*args):
            for arg, value, allowed_types in zip(names, args[1:], typedefs):
                if not isinstance(value, allowed_types):
                    one_of = ''
                    if hasattr(allowed_types, '__len__'):
                        one_of = "one of "
                    msg = ".%s() argument %r must be %s%s"
                    msg %= fcn.__name__, arg, one_of, allowed_types
                    raise TypeError(msg)
            return fcn(*args)
        return check_args
    return argchecker

class MyClass(object):
    """My example class."""
    @method_argtypes(int)
    def __init__(self, int_member = 0):
        """Instantiate a new MyClass object.
        
        IN:
        int_member = 0: <int>
            Set the value for the data member. Must be int.
             
        """
        # Initiallization starts here. May for example contain assignment.
        self.int_member = int_member

    @method_argtypes(int)
    def process_data(self, data):
        """Do some data processing.
        
        IN:
        data: <int>
            New information that should be incorporated. Must be int.
        
        """
        # Data processing starts here. May for example contain addition:
        self.int_member += data

# Test code. Decomment to play. :-)

#a = MyClass('moo')
#a = MyClass(9)
#a.int_member = 'moo'
#a.process_data('moo')
#a.process_data(9)
