from collections import defaultdict

from .exceptions import UndefinedOperatorError, DifferentLengthError
from .exceptions import ExpectedTypeError


def args_equal_length(*arg_ids):
    def checker(func):
        def do_check(*args):
            for arg_tuple in arg_ids:
                for arg in arg_tuple:
                    if not hasattr(args[arg], '__len__'):
                        raise ExpectedTypeError(args[arg], ['string', 'list', 'tuple'])
                    if len(args[arg]) != len(args[arg_tuple[0]]):
                        raise DifferentLengthError(args, arg_tuple, arg)
            return func(*args)
        return do_check
    return checker


def arg_is_sequence(*arg_ids):
    def checker(func):
        def do_check(*args):
            for arg_id in arg_ids:
                if not hasattr(args[arg_id], '__iter__') or not hasattr(args[arg_id], '__getitem__'):
                    raise ExpectedTypeError(args[arg_id], ['string', 'list', 'tuple'])
            return func(*args)
        return do_check
    return checker


def arg_is_float(*arg_ids):
    def checker(func):
        def do_check(*args):
            for arg_id in arg_ids:
                if not isinstance(args[arg_id], float):
                    raise ExpectedTypeError(args[arg_id], ['float'])
            return func(*args)
        return do_check
    return checker


def operator_is_defined(op):
    def checker(func):
        def do_check(self, other):
            if op not in self._operators:
                raise UndefinedOperatorError(op, self, other, self._operators)
            else:
                return func(self, other)
        return do_check
    return checker


def arg_has_shape(arg_id, shape):
    '''Check that a particular argument is an array with a given shape. The
    shape may contain string attributes, which will be fetched from arg0 to
    the function (usually self).
    '''
    arg_id -= 1
    def checker(method):
        def do_check(self, *args, **kwargs):
            arg = args[arg_id]
            if not hasattr(arg, 'shape'):
                raise Exception
            shape_values = []
            for dim in shape:
                if not isinstance(dim, int):
                    dim = getattr(self, dim, None)
                shape_values.append(dim)
            for i, dim in enumerate(shape_values):
                # Allow underspecified dimensions
                if dim is not None and arg.shape[i] != dim:
                    raise Exception("Shape mismatch", dim, arg.shape)
            return method(self, *args, **kwargs)
        return do_check
    return checker


def is_sequence(arg):
    return True


def equal_lengths(*args, **kwargs):
    return True


def not_empty(arg):
    return True


def value(min=0, max=None):
    def constraint(arg):
        return True
    return constraint


def arg(arg_id, *constraints, **constraint_kwargs):
    def arg_check_adder(func):
        def wrapper(*args, **kwargs):
            for check in constraints:
                check(args[arg_id], **constraint_kwargs)
            return func(*args, **kwargs)
        return wrapper
    return arg_check_adder


def args(arg_ids, *constraints, **constraint_kwargs):
    def arg_check_adder(func):
        def wrapper(*args, **kwargs):
            for check in constraints:
                check(*[args[i] for i in arg_ids], **constraint_kwargs)
            return func(*args, **kwargs)
        return wrapper
    return arg_check_adder

@@ -168,32 +168,100 @@
 ngth(*arg_ids):%0A
+    '''Check that a tuple of arguments has the same length.%0A    '''%0A
     def checker(
@@ -725,32 +725,1043 @@
 eturn checker%0A%0A%0A
+def arg_has_shape(arg_id, shape):%0A    '''Check that a particular argument is an array with a given shape. The%0A    shape may contain string attributes, which will be fetched from arg0 to%0A    the function (usually self).%0A    '''%0A    arg_id -= 1%0A    def checker(method):%0A        def do_check(self, *args, **kwargs):%0A            arg = args%5Barg_id%5D%0A            if not hasattr(arg, 'shape'):%0A                raise ExpectedTypeError(arg, %5B'array'%5D)%0A            shape_values = %5B%5D%0A            for dim in shape:%0A                if not isinstance(dim, int):%0A                    dim = getattr(self, dim, None)%0A                shape_values.append(dim)%0A            for i, dim in enumerate(shape_values):%0A                # Allow underspecified dimensions%0A                if dim is not None and arg.shape%5Bi%5D != dim:%0A                    raise ShapeMismatchError()%0A                    raise Exception(%22Shape mismatch%22, dim, arg.shape)%0A            return method(self, *args, **kwargs)%0A        return do_check%0A    return checker%0A%0A%0A
 def arg_is_seque
@@ -2759,948 +2759,8 @@
 r%0A%0A%0A
-def arg_has_shape(arg_id, shape):%0A    '''Check that a particular argument is an array with a given shape. The%0A    shape may contain string attributes, which will be fetched from arg0 to%0A    the function (usually self).%0A    '''%0A    arg_id -= 1%0A    def checker(method):%0A        def do_check(self, *args, **kwargs):%0A            arg = args%5Barg_id%5D%0A            if not hasattr(arg, 'shape'):%0A                raise Exception%0A            shape_values = %5B%5D%0A            for dim in shape:%0A                if not isinstance(dim, int):%0A                    dim = getattr(self, dim, None)%0A                shape_values.append(dim)%0A            for i, dim in enumerate(shape_values):%0A                # Allow underspecified dimensions%0A                if dim is not None and arg.shape%5Bi%5D != dim:%0A                    raise Exception(%22Shape mismatch%22, dim, arg.shape)%0A            return method(self, *args, **kwargs)%0A        return do_check%0A    return checker%0A%0A%0A
 def 

Update check module