HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Strict types decorator (works only with Python 3.5)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withworkspythontypesdecoratorstrictonly

Problem

I wrote a decorator which makes Python 3.5 raise exceptions if the arguments that are passed to a type-hinted function are of the wrong type.

from typing import get_type_hints

def strict_types(f):
    def type_checker(*args, **kwargs):
        hints = get_type_hints(f)

        all_args = kwargs.copy()
        all_args.update(dict(zip(f.__code__.co_varnames, args)))

        for key in all_args:
            if key in hints:
                if type(all_args[key]) != hints[key]:
                    raise Exception('Type of {} is {} and not {}'.format(key, type(all_args[key]), hints[key]))

        res = f(*args, **kwargs)

        if type(res) == hints['return']:
            return res
        else:
            raise Exception('Type of result is {} and not {}'.format(type(res), hints['return']))

    return type_checker


It's used like this:

@strict_types
def concatenate_with_spam(text: str) -> str:
    return text + 'spam'


and if something of the wrong type is passed to the decorated function e.g. concatenate_with_spam(42) an exception is raised:

Exception: Type of text is  and not 


The part that I'm worried about is the part that checks the result. It calls the original function (to learn result of what type it returns), but returns a wrapped function type_checker that will be called one more time, so the runtime of function is (at least) multiplied by 2. Are there any other things I have missed? What can be improved here (except for not using this not-pythonic decorator and crucifying me for writing it)?

Solution

You should change this part a bit:

for key in all_args:
        if key in hints:
            if type(all_args[key]) != hints[key]:
                raise Exception('Type of {} is {} and not {}'.format(key, type(all_args[key]), hints[key]))


Here, you are silently ignoring missing type hints, but if the user went as far as to use this decorator, you should raise an exception instead, like this:

for key in all_args:
        try:
            if type(all_args[key]) != hints[key]:
                raise Exception('Type of {} is {} and not {}'.format(key, type(all_args[key]), hints[key]))
        except IndexError:
                raise TypeError('The formal parameter {} was not given a type'.format(key))


Specific Errors & Exceptions

Exception is hilariously un-informative, use TypeError to convey more information.

Order of conditionals

if type(res) == hints['return']:
        return res
    else:
        raise Exception('Type of result is {} and not {}'.format(type(res), hints['return']))


Here, the real logic is in the else, but I prefer seeing the main logic after the if, like this:

if type(res) != hints['return']:
    raise Exception('Type of result is {} and not {}'.format(type(res), hints['return']))
return res


Now it looks more like a simple perturbation of an almost linear function, than a full-blown logic statement.

Please do not abbreviate

  • res -> result



  • func -> function



It takes 0.5 seconds more to type and a tenth of the time to read.

Code Snippets

for key in all_args:
        if key in hints:
            if type(all_args[key]) != hints[key]:
                raise Exception('Type of {} is {} and not {}'.format(key, type(all_args[key]), hints[key]))
for key in all_args:
        try:
            if type(all_args[key]) != hints[key]:
                raise Exception('Type of {} is {} and not {}'.format(key, type(all_args[key]), hints[key]))
        except IndexError:
                raise TypeError('The formal parameter {} was not given a type'.format(key))
if type(res) == hints['return']:
        return res
    else:
        raise Exception('Type of result is {} and not {}'.format(type(res), hints['return']))
if type(res) != hints['return']:
    raise Exception('Type of result is {} and not {}'.format(type(res), hints['return']))
return res

Context

StackExchange Code Review Q#105758, answer score: 3

Revisions (0)

No revisions yet.