Skip to main content
edited tags
Link
200_success
  • 145.7k
  • 22
  • 191
  • 481
Source Link

Python Decorator - inspecting function argument values

Some of my functions use a "fail_silently" flag. It is used in the following way:

def test(a, b, c=1, fail_silently = False)
    try:
        return int(a) + c
    except:
       if fail_silently:
           return None
       raise

Therefore, if there is an error, we catch it and fail gracefully. The key here is that it can be toggled dynamically by whoever is calling it.

I am using this for a many different functions and class methods and thought to make it a decorator.

There are a few problems:

  1. I want to be able name the flag "raise_exception" or "fail_silently" or "whatever"...
  2. The flag may or may not have a default value
  3. The flag may or may not be passed in (usually not)
  4. It needs to work with class methods too

Thus my decorator needs to look for (assuming the flag is called "fail_silently") the flag in the following locations in this order

  1. **kwargs (the passed in function arguments), simple dictionary get on flag
  2. *args - get the positional argument of the flag, then scan for index in args (which might not be there)
  3. Get the default value of the flag from the function

The problem is the code is now getting really messy and has many points of failure.

def try_except_response(parameter = 'fail_silently'):
    def real_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs) # the function itself, 
            except: # if it raises an Exception!
                # check to see if the Flag is in passed in kwargs!
                if kwargs.get(parameter)== True: 
                    return None
                elif kwargs.get(parameter) == False:
                    raise
                else:
                    # Flag is not in kwargs, check to see if it is in args
                    function_args, vargs, kewords, defaults = inspect.getargspec(func) # get the index of argument of interest
                    try:
                        argument_index = function_args.index(parameter)  # get the index of argument of interest
                    except ValueError:
                        raise ValueError('The inputted decorator for "fail_silently" was not found!  The behavior will not work.')

                    if len(args) > argument_index: # check to see if it is inputted
                        
                        keyword = args[argument_index] # get the value!!
                        if keyword == True: 
                            return None
                        elif kwargs == False:
                            raise
                        else:
                            raise ValueError('The "fail_silently" flag did not return a value that we understand.  Must be True or False')
                        
                    else:
                        # not in args either! let's go get the default value.
                        # TODO MORE STUFF!!!
                        raise
                    raise
                raise
        return wrapper

What is the best way this can be cleaned up? Or alternatives? I am thinking about implementing a version of locals() that will create a dictionary of all parameters passed into the function and then check from there...

There are simplifying assumptions we can make, but that would require the person writing the function to know something ('flag is always last argument, or default value is always False...'), but I am hoping to get the decorator to be as transparent as possible so it just "works" and lets the function be a function (or class method)