2
\$\begingroup\$

I wrote some type checking decorators for Python. One checks the type that a function returns, the other checks the type that a function accepts.

import functools

def returns(t):
""" Calls a function, and if it returns something of the wrong type,
    throws a TypeError
"""
    def outer(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            output = func(*args, **kwargs)
            if not isinstance(output, t):
                message = "function {} returned an invalid type: {} is a {}"
                raise TypeError(message.format(func.__name__,
                                               output,
                                               type(output)))
            else:
                return output
        return wrapper
    return outer

class accepts:
    def __init__(self, *artypes, **kwtypes):
        """ Pass it a list consisting of types or tuples of types.
        in other words, pass it things that will be valid second
        arguments to the isinstance(arg, type) method.
        """
        self.artypes = artypes
        self.kwtypes = kwtypes

    def checkTypes(self, args, kwargs):
        """ Checks each arg and kwarg for type validity, and if
            they fail, then it throws a TypeError. Not very descriptive,
            but it's just an example.
        """
        type_zip = zip(args, self.artypes)
        for arg, type_ in type_zip:
            if not isinstance(arg, type_):
                raise ValueError()

        for key, value in kwargs.items():
            if not isinstance(value, self.kwtypes[key]):
                raise ValueError()

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            self.checkTypes(args, kwargs)
            return func(*args, **kwargs)
        return wrapper

@accepts(int)
def hitimes(times=1):
    return "hello " * times


@accepts(int, word=str)
def byetimes(times, word="bye"):
    return " ".join([word] * times)

print(hitimes(2)) # works
print(byetimes(5)) # works
print(byetimes(3, word="adios")) # works as expected
print(byetimes(3, word=[])) # fails as expected

For returns, I used nested functions, and for accepts I used the class style of decorator. But both of them get pretty unwieldy with respect to indentation levels. What can I do to make these cleaner?

Note that the wrappers are themselves pretty pointless.

\$\endgroup\$
  • 1
    \$\begingroup\$ accepts shouldn’t be a class. \$\endgroup\$ – Ry- Aug 16 '13 at 20:00
  • \$\begingroup\$ @minitech why not? \$\endgroup\$ – scott_fakename Aug 17 '13 at 6:48
  • \$\begingroup\$ What does it represent? You’re just using it for variables, which can be accomplished by using variables… \$\endgroup\$ – Ry- Aug 17 '13 at 15:28
5
\$\begingroup\$

I agree with @minitech that accepts shouldn't be a class. It can be written much more compactly using a nested decorator (like returns, in fact):

def accepts(*artypes, **kwtypes):
    """
    Pass it a list consisting of types or tuples of types.
    in other words, pass it things that will be valid second
    arguments to the isinstance(arg, type) method.
    """
    def outer(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            type_zip = zip(args, artypes)
            for arg, type_ in type_zip:
                if not isinstance(arg, type_):
                    raise ValueError()

            for key, value in kwargs.items():
                if not isinstance(value, kwtypes[key]):
                    raise ValueError()
            return func(*args, **kwargs)
        return wrapper
    return accepts_wrapper

This then works equivalently to returns, and is much shorter (as you need less variable-passing code).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.