HiveBrain v1.2.0
Get Started
← Back to all entries
debugpythonMinor

Python Decorator - inspecting function argument values

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
argumentfunctionpythoninspectingvaluesdecorator

Problem

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:

  • I want to be able name the flag "raise_exception" or "fail_silently" or "whatever"...



  • The flag may or may not have a default value



  • The flag may or may not be passed in (usually not)



  • 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

  • **kwargs (the passed in function arguments), simple dictionary get on flag



  • *args - get the positional argument of the flag, then scan for index in args (which might not be there)



  • 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:

Solution

I'm not a fan of this design. @TheBlackCat is right. Don't do this.

If you are to do this, rethink your design. A more sensible one might look like

import functools
from contextlib import suppress

def try_except_response(func):
    @functools.wraps(func)
    def wrapper(*args, fail_silently, **kwargs):
        if fail_silently:
            with suppress(Exception):
                return func(*args, **kwargs)
        else:
            return func(*args, **kwargs)

    return wrapper


The key principle here is KISS.

Note that I'm suppressing Exception, not BaseException, since catching SystemExit and KeyboardInterrupt is almost always a bad idea.

If you want to make fail_silently a default argument, just use another wrapper:

def default_fail_silently(default):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, fail_silently=default, **kwargs):
            return func(*args, fail_silently=fail_silently, **kwargs)
        return wrapper
    return decorator


Then you can do

@default_fail_silently(False)
@try_except_response
def test(a, b, c=1)
    return int(a) + c


This does mean there's more boilerplate, since it exists for each piece of functionality you add, but the logic itself is an order of magnitude simpler.

Code Snippets

import functools
from contextlib import suppress

def try_except_response(func):
    @functools.wraps(func)
    def wrapper(*args, fail_silently, **kwargs):
        if fail_silently:
            with suppress(Exception):
                return func(*args, **kwargs)
        else:
            return func(*args, **kwargs)

    return wrapper
def default_fail_silently(default):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, fail_silently=default, **kwargs):
            return func(*args, fail_silently=fail_silently, **kwargs)
        return wrapper
    return decorator
@default_fail_silently(False)
@try_except_response
def test(a, b, c=1)
    return int(a) + c

Context

StackExchange Code Review Q#95124, answer score: 6

Revisions (0)

No revisions yet.