debugpythonMinor
Python Decorator - inspecting function argument values
Viewed 0 times
argumentfunctionpythoninspectingvaluesdecorator
Problem
Some of my functions use a "fail_silently" flag. It is used in the following way:
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:
Thus my decorator needs to look for (assuming the flag is called "fail_silently")
the flag in the following locations in this order
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:
def test(a, b, c=1, fail_silently = False)
try:
return int(a) + c
except:
if fail_silently:
return None
raiseTherefore, 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
The key principle here is KISS.
Note that I'm suppressing
If you want to make
Then you can do
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.
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 wrapperThe 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 decoratorThen you can do
@default_fail_silently(False)
@try_except_response
def test(a, b, c=1)
return int(a) + cThis 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 wrapperdef 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) + cContext
StackExchange Code Review Q#95124, answer score: 6
Revisions (0)
No revisions yet.