Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it Pythonic to check function argument types?

Tags:

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?

like image 847
Brad Zeis Avatar asked Dec 23 '09 02:12

Brad Zeis


People also ask

Can you specify types in Python?

Recent versions of Python allow you to specify explicit type hints that can be used by different tools to help you develop your code more efficiently. In this tutorial, you'll learn about the following: Type annotations and type hints. Adding static types to code, both your code and the code of others.

What are the 4 types of arguments in Python?

5 Types of Arguments in Python Function Definition:positional arguments. arbitrary positional arguments. arbitrary keyword arguments.

How do you validate an argument in Python?

If your function's contract is that arg 'a' MUST be an int between 0 and 10 and argument 'b' MUST be a non-empty string, then raise the appropriate exception types, ie TypeError or ValueError - try int('a') and int(None) in your Python shell.

Why should we generally verify the arguments are what we expect when writing a function?

The Importance of Evaluating Argument Functions Before we compute the logic of our function, it's often a good idea to first validate that the arguments passed to the function meet our criteria. For example, if we had a function that calculated the area of a square, we can't compute the area given a side lenght of -2.


2 Answers

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.

like image 130
Ned Batchelder Avatar answered Nov 10 '22 10:11

Ned Batchelder


One of the reasons Duck Typing is encouraged in Python is that someone might wrap one of your objects, and then it will look like the wrong type, but still work.

Here is an example of a class that wraps an object. A LoggedObject acts in all ways like the object it wraps, but when you call the LoggedObject, it logs the call before performing the call.

from somewhere import log from myclass import A  class LoggedObject(object):     def __init__(self, obj, name=None):         if name is None:             self.name = str(id(obj))         else:             self.name = name         self.obj = obj     def __call__(self, *args, **kwargs):         log("%s: called with %d args" % (self.name, len(args)))         return self.obj(*args, **kwargs)  a = LoggedObject(A(), name="a") a(1, 2, 3)  # calls: log("a: called with 3 args") 

If you explicitly test for isinstance(a, A) it will fail, because a is an instance of LoggedObject. If you just let the duck typing do its thing, this will work.

If someone passes the wrong kind of object by mistake, some exception like AttributeError will be raised. The exception might be clearer if you check for types explicitly, but I think overall this case is a win for duck typing.

There are times when you really need to test the type. The one I learned recently is: when you are writing code that works with sequences, sometimes you really need to know if you have a string, or it's any other kind of sequence. Consider this:

def llen(arg):     try:         return max(len(arg), max(llen(x) for x in arg))     except TypeError: # catch error when len() fails         return 0 # not a sequence so length is 0 

This is supposed to return the longest length of a sequence, or any sequence nested inside it. It works:

lst = [0, 1, [0, 1, 2], [0, 1, 2, 3, 4, 5, 6]] llen(lst)  # returns 7 

But if you call llen("foo"), it will recurse forever until stack overflow.

The problem is that strings have the special property that they always act like a sequence, even when you take the smallest element from the string; a one-character string is still a sequence. So we cannot write llen() without an explicit test for a string.

def llen(arg):     if isinstance(arg, str):  # Python 3.x; for 2.x use isinstance(arg, basestring)         return len(arg)     try:         return max(len(arg), max(llen(x) for x in arg))     except TypeError: # catch error when len() fails         return 0 # not a sequence so length is 0 
like image 35
steveha Avatar answered Nov 10 '22 11:11

steveha