Question

I am writing a framework that calls into code written by other people (the framework plays Monopoly and calls into player AIs). The AIs tell the framework what to do in the return values of function calls.

I would like to check the types of the return values to make sure that they won't blow up my framework code.

For example:

instructions = player.sell_houses()
# Process the instructions...

In this example I am expecting the player to return a list of tuples, such as:

[(Square.BOW_STREET, 2), (Square.MARLBOROUGH_STREET, 2), (Square.VINE_STREET, 1)]

Is there a simpl(ish) way to validate what the AI returns to me? I'm imagining something like this:

    instructions = player.sell_houses()
    if not typecheck(instructions, [(str, int)]):
        # Data was not valid...

I don't just want to check that the returned data is a list. I want to check that it is a list of a specific type. In the example, it is a list of tuples where each tuple holds a string and an integer.

I've seen that a lot of the Python type-checking questions get the answer "type checking is evil". If so, what should I be doing in this case? There doesn't seem to be anything stopping the AI code from returning absolutely anything, and I have to be able to validate or cope with it in some way.


Edit: I could check this 'by hand' by writing a function. For instructions above it could be something like this:

def is_valid(instructions):
    if not isinstance(instructions, list): return False
    for item in instructions:
        if not isinstance(item, tuple): return False
        if len(item) != 2: return False
        if not isinstance(item[0], str): return False
        if not isinstance(item[1], int): return False
    return True

But in this case, I would have to write a similarly complex validation function for each type of value I need to validate. So I'm wondering if a more generic validation function or library exists where I can give it an expression (like [(str, int)]) and it will validate against it without having to do the work by hand.

Was it helpful?

Solution

Sorry to answer my own question. It looks from the answers so far that there may not be a library function that does this, so I've written one:

def is_iterable(object):
    '''
    Returns True if the object is iterable, False if it is not.
    '''
    try:
        i = iter(object)
    except TypeError:
        return False
    else:
        return True


def validate_type(object, type_or_prototype):
    '''
    Returns True if the object is of the type passed in.

    The type can be a straightforward type, such as int, list, or
    a class type. If so, we check that the object is an instance of
    the type.

    Alternatively the 'type' can be a prototype instance of a more
    complex type. For example:
    [int]                   a list of ints
    [(str, int)]            a list of (str, int) tuples
    {str: [(float, float)]} a dictionary of strings to lists of (float, float) tuples

    In these cases we recursively check the sub-items to see if they match
    the prototype.
    '''
    # If the type_or_prototype is a type, we can check directly against it...
    type_of_type = type(type_or_prototype)
    if type_of_type == type:
        return isinstance(object, type_or_prototype)

    # We have a prototype.

    # We check that the object is of the right type...
    if not isinstance(object, type_of_type):
        return False

    # We check each sub-item in object to see if it is of the right sub-type...
    if(isinstance(object, dict)):
        # The object is a dictionary, so we check that its items match
        # the prototype...
        prototype = type_or_prototype.popitem()
        for sub_item in object.items():
            if not validate_type(sub_item, prototype):
                return False

    elif(isinstance(object, tuple)):
        # For tuples, we check that each element of the tuple is
        # of the same type as each element the prototype...
        if len(object) != len(type_or_prototype):
            return False
        for i in range(len(object)):
            if not validate_type(object[i], type_or_prototype[i]):
                return False

    elif is_iterable(object):
        # The object is a non-dictionary collection such as a list or set.
        # For these, we check that all items in the object match the
        prototype = iter(type_or_prototype).__next__()
        for sub_item in object:
            if not validate_type(sub_item, prototype):
                return False

    else:
        # We don't know how to check this object...
        raise Exception("Can not validate this object")

    return True

You can use this like isinstance with simple types, e.g:

validate_type(3.4, float)
Out[1]: True

Or with more complex, embedded types:

list1 = [("hello", 2), ("world", 3)]
validate_type(list1, [(str, int)])
Out[2]: True

OTHER TIPS

Well you can use the type function:

>>> type("abc") in [str, tuple]
True

Although aIKid's answer will do the same thing.

I think you're looking for isinstance:

isinstance(instruction, (str, int))

Which will return True if instruction is an instance of str or int.

To check it all, you can use the all() function:

all(isinstance(e[0], (str, int)) for e in instructions)

This will check if all of the first element of your tuples are an instance of str or int. Returns False if any of it is invalid.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top