Is it Pythonic to check function argument types?

Brad Zeis picture Brad Zeis · Dec 23, 2009 · Viewed 13.7k times · Source

I know, type checking function arguments is generally frowned upon in Python, but I think I've come up with a situation where it makes sense to do so.

In my project I have an Abstract Base Class Coord, with a subclass Vector, which has more features like rotation, changing magnitude, etc. Lists and tuples of numbers will also return True for isinstance(x, Coord). I also have many functions and methods that accept these Coord types as arguments. I've set up decorators to check the arguments of these methods. Here is a simplified version:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):
            for i in len(args):
                if not isinstance(args[i], self.types[i]):
                    raise TypeError

            return func(*args)

        return wrapper

This version is very simple, it still has some bugs. It's just there to illustrate the point. And it would be used like:

@accepts(numbers.Number, numbers.Number)
def add(x, y):
    return x + y

Note: I'm only checking argument types against Abstract Base Classes.

Is this a good idea? Is there a better way to do it without having to repeat similar code in every method?

Edit:

What if I were to do the same thing, but instead of checking the types beforehand in the decorator, I catch the exceptions in the decorator:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):

            try:
                return func(*args)
            except TypeError:
                raise TypeError, message
            except AttributeError:
                raise AttributeError, message

        return wrapper

Is that any better?

Answer

Ned Batchelder picture Ned Batchelder · Dec 23, 2009

Your taste may vary, but the Pythonic(tm) style is to just go ahead and use objects as you need to. If they don't support the operations you're attempting, an exception will be raised. This is known as duck typing.

There are a few reasons for favoring this style: first, it enables polymorphism by allowing you to use new kinds of objects with existing code so long as the new objects support the right operations. Second, it streamlines the successful path by avoiding numerous checks.

Of course, the error message you get when using wrong arguments will be clearer with type checking than with duck typing, but as I say, your taste may vary.