简体   繁体   中英

Python decorator for checking the return type: Especially check for nested things like List[List[List[str]]]

I'm trying to write my one python decorator for type checking. It works well, but struggles with nested type hints. For example take

@type_check
def fun(x: int) -> List[List[List[str]]]:
    return [[[str(x)]]]

fun(x=42)

I have a decorator, which eveluates the function and checks, if the actual return value has the expected type:

import inspect
# inside the decorator
actual_result = func(*args, **kwargs)              # [[['42']]]
expected_result_type = spec.annotations['return']  # typing.List[typing.List[typing.List[str]]]

And now is challenge to check is [[['42']]] has type typing.List[typing.List[typing.List[str]]] . How can I do that?

All I found out, is that [[['42']]] has simply type <class 'list'> which ignores the information about nesting completly. And I can check this with

if hasattr(expected_result_type, '__origin__'):
    expected_result_type = expected_result_type.__origin__

if expected_result_type is not None:
    assert isinstance(result, expected_result_type)
else: # None is kind of a special case
    assert result is expected_result_type

This works on the "outer layer", but ignores nested type hints. Is there any way to check all layers?

The simple answer would be:

Simply recurse into all arguments like:

def type_check(value, annotation):
    if isinstance(annotation, type):
        return isinstance(value, annotation)
    elif annotation == typing.T or annotation == typing.Any:
        return True
    elif isinstance(annotation, typing._GenericAlias):
        if annotation.__origin__ == list:
            if not isinstance(value, list):
                return False
            inner_annotation = annotation.__args__[0]
            return all(type_check(val, inner_annotation) for val in value)

Example

>>> type_check(5, int)
True
>>> type_check([1, 2, 3], typing.List[int])
True
>>> type_check([1, 2, 3], typing.List[float])
False

Which you can simply use in you decorator. It might be improved to handle Dict, Tuple and other common types.

However it's just a toy example, if you want to do robust dynamic type checking, you'll have to handle Generator , Callable , Iterator etc... which is not only quite complex but also in some case completely impossible (Iterators for example can't be checked unless iterated over).

Have a look at the enforce project which is exactly that: a python dynamic type checking using decorators. The typing module source code is also really interesting to read, but really pushes the limits of what dynamic introspection can do.

Hope it helps;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM