Best Way to Check Function Arguments

Best way to check function arguments?

The most Pythonic idiom is to clearly document what the function expects and then just try to use whatever gets passed to your function and either let exceptions propagate or just catch attribute errors and raise a TypeError instead. Type-checking should be avoided as much as possible as it goes against duck-typing. Value testing can be OK – depending on the context.

The only place where validation really makes sense is at system or subsystem entry point, such as web forms, command line arguments, etc. Everywhere else, as long as your functions are properly documented, it's the caller's responsibility to pass appropriate arguments.

How to check which arguments a function/method takes?

You can use inspect.getargspec() to see what arguments are accepted, and any default values for keyword arguments.

Demo:

>>> def foo(bar, baz, spam='eggs', **kw): pass
...
>>> import inspect
>>> inspect.getargspec(foo)
ArgSpec(args=['bar', 'baz', 'spam'], varargs=None, keywords='kw', defaults=('eggs',))
>>> inspect.getargspec(foo).args
['bar', 'baz', 'spam']

In Python 3, you want to use inspect.getfullargspec() as this method supports new Python 3 function argument features:

>>> def foo(bar: str, baz: list, spam: str = 'eggs', *, monty: str = 'python', **kw) -> None: pass
...
>>> import inspect
>>> inspect.getfullargspec(foo)
FullArgSpec(args=['bar', 'baz', 'spam'], varargs=None, varkw='kw', defaults=('eggs',), kwonlyargs=['monty'], kwonlydefaults={'monty': 'python'}, annotations={'baz': <class 'list'>, 'return': None, 'spam': <class 'str'>, 'monty': <class 'str'>, 'bar': <class 'str'>})

inspect.getargspec() should be considered deprecated in Python 3.

Python 3.4 adds the inspect.Signature() object:

>>> inspect.signature(foo)
<inspect.Signature object at 0x100bda588>
>>> str(inspect.signature(foo))
"(bar:str, baz:list, spam:str='eggs', *, monty:str='python', **kw) -> None"
>>> inspect.signature(foo).parameters
mappingproxy(OrderedDict([('bar', <Parameter at 0x100bd67c8 'bar'>), ('baz', <Parameter at 0x100bd6ea8 'baz'>), ('spam', <Parameter at 0x100bd69f8 'spam'>), ('monty', <Parameter at 0x100bd6c28 'monty'>), ('kw', <Parameter at 0x100bd6548 'kw'>)]))

and many more interesting options to play with signatures.

Should I check every single parameter of a function to make sure the function works well?

I'd like to argue that not checking pointers for NULL in library functions that expect valid pointers is actually better practice than to do error returns or silently ignoring them.

NULL is not the only invalid pointer. There are billions of other pointer values that are actually incorrect, why should we give preferential treatment to just one value?

Error returns are often ignored, misunderstood or mismanaged. Forgetting to check one error return could lead to a misbehaving program. I'd like to argue that a program that silently misbehaves is worse than a program that doesn't work at all. Incorrect results can be worse than no results.

Failing early and hard eases debugging. This is the biggest reason. An end user of a program doesn't want the program to crash, but as a programmer I'm the end user of a library and I actually want it to crash. Crashing makes it evident that there's a bug I need to fix and the faster we hit the bug and the closer the crash is to the source of the bug, the faster and easier I can find it and fix it. A NULL pointer dereference is one of the most trivial bugs to catch, debug and fix. It's much easier than trawling through gigabytes of logs to spot one line that says "create_something had a null pointer".

With error returns, what if the caller catches that error, returns an error itself (in your example that would be err_create_something_failed) and its caller returns another error (err_caller_of_create_something_failed)? Then you have an error return 3 functions away, that might not even indicate what actually went wrong. And even if it manages to indicate what actually went wrong (by having a whole framework for error handling that records exactly where the error happened through the whole chain of callers) the only thing you can do with it is to look up the error value in some table and from that conclude that there was a NULL pointer in create_something. It's a lot of pain when instead you could just have opened a debugger and seen exactly where the assumption was violated and what exact chain of function calls lead to that problem.

In the same spirit you can use assert to validate other function arguments to cause early and easy to debug failures. Crash on the assert and you have the full correct call chain that leads to the problem. I just wouldn't use asserts to check pointers because it's pointless (at least on an operating system with memory management) and makes things slower while giving you the same behavior (minus the printed message).

Is it Pythonic to check function argument types?

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.

function arguments validation: what is the pythonic way?

  1. Your validation error is considered pythonic:
def make_fruits_lower_case(list_of_fruits):
if isinstance(list_of_fruits, list):
return [fruit.lower() for fruit in list_of_fruits]
raise TypeError('list_of_fruits must be a of type list')

But in order to make it clear that your function takes in a list, you can specify the type of input parameters required (and output type to expect):

def make_fruits_lower_case(list_of_fruits : list) -> list:
if isinstance(list_of_fruits, list):
return [fruit.lower() for fruit in list_of_fruits]
raise TypeError('list_of_fruits must be a of type list')

How to use Python decorators to check function arguments?

From the Decorators for Functions and Methods:

Python 2

def accepts(*types):
def check_accepts(f):
assert len(types) == f.func_code.co_argcount
def new_f(*args, **kwds):
for (a, t) in zip(args, types):
assert isinstance(a, t), \
"arg %r does not match %s" % (a,t)
return f(*args, **kwds)
new_f.func_name = f.func_name
return new_f
return check_accepts

Python 3

In Python 3 func_code has changed to __code__ and func_name has changed to __name__.

def accepts(*types):
def check_accepts(f):
assert len(types) == f.__code__.co_argcount
def new_f(*args, **kwds):
for (a, t) in zip(args, types):
assert isinstance(a, t), \
"arg %r does not match %s" % (a,t)
return f(*args, **kwds)
new_f.__name__ = f.__name__
return new_f
return check_accepts

Usage:

@accepts(int, (int,float))
def func(arg1, arg2):
return arg1 * arg2

func(3, 2) # -> 6
func('3', 2) # -> AssertionError: arg '3' does not match <type 'int'>

arg2 can be either int or float

What is the best practices way to check if optional arguments are used in a function call in julia

function sum_these(x, y=nothing)
if y == nothing
do stuff
end
return something
end

That's not only perfectly fine, but because nothing is a singleton of type Void, the y==nothing will actually compile away so the if statement is actually no runtime cost here. I talk about this in depth in a blog post, but what it really means is that function auto-specialization allows for checks against nothing to always be free in type-stable/inferrable functions.

However, you may want to consider splitting this into two different functions:

function sum_these(x)
return something
end

function sum_these(x, y)
do stuff
return something
end

Of course, this is just a style difference and the right choice is determined by how much code is shared in the return something.

How can I find the number of arguments of a Python function?

The previously accepted answer has been deprecated as of Python 3.0. Instead of using inspect.getargspec you should now opt for the Signature class which superseded it.

Creating a Signature for the function is easy via the signature function:

from inspect import signature

def someMethod(self, arg1, kwarg1=None):
pass

sig = signature(someMethod)

Now, you can either view its parameters quickly by string it:

str(sig)  # returns: '(self, arg1, kwarg1=None)'

or you can also get a mapping of attribute names to parameter objects via sig.parameters.

params = sig.parameters 
print(params['kwarg1']) # prints: kwarg1=20

Additionally, you can call len on sig.parameters to also see the number of arguments this function requires:

print(len(params))  # 3

Each entry in the params mapping is actually a Parameter object that has further attributes making your life easier. For example, grabbing a parameter and viewing its default value is now easily performed with:

kwarg1 = params['kwarg1']
kwarg1.default # returns: None

similarly for the rest of the objects contained in parameters.


As for Python 2.x users, while inspect.getargspec isn't deprecated, the language will soon be :-). The Signature class isn't available in the 2.x series and won't be. So you still need to work with inspect.getargspec.

As for transitioning between Python 2 and 3, if you have code that relies on the interface of getargspec in Python 2 and switching to signature in 3 is too difficult, you do have the valuable option of using inspect.getfullargspec. It offers a similar interface to getargspec (a single callable argument) in order to grab the arguments of a function while also handling some additional cases that getargspec doesn't:

from inspect import getfullargspec

def someMethod(self, arg1, kwarg1=None):
pass

args = getfullargspec(someMethod)

As with getargspec, getfullargspec returns a NamedTuple which contains the arguments.

print(args)
FullArgSpec(args=['self', 'arg1', 'kwarg1'], varargs=None, varkw=None, defaults=(None,), kwonlyargs=[], kwonlydefaults=None, annotations={})


Related Topics



Leave a reply



Submit