import pandas as pd
import wrapt
def _apply_function_to_named_arg(func, arg_name):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
# Get argument names
code = wrapped.__code__
names = code.co_varnames[:code.co_argcount]
# Convert both args and kwargs to a kwargs-style dict called all_args
all_args = kwargs.copy() # copy keyword args
for n, v in zip(names, args): # append positional args
all_args[n] = v
# Apply the transformation defined by `func` to the named arg
all_args[arg_name] = func(all_args[arg_name])
# Call the wrapped function and return its output
return wrapped(**all_args)
return wrapper
def negate_var(arg_name):
return _apply_function_to_named_arg(lambda x: -x, arg_name)
@negate_var("offset")
def foo(abc, offset=1):
return abc + offset
# Monkey-patch
pd.Series.foo = foo
# Test if it works
s = pd.Series([1, 2, 3, 4, 5])
print('Input\n-----')
print(s)
print('\nOutput of method call\n---------------------')
print(s.foo(offset=3))
print('\nOutput of function call\n-----------------------')
print(foo(s, offset=3))
## General example functions
def decorator_with_args_and_kwargs(*dec_args,**dec_kwargs):
def wrap(f):
print("Wrapping function...")
def wrapped_f(*args, **kwargs):
print("Inside wrapped function")
print("Decorator args:", dec_args)
print("Function args:", args)
print("Decorator kwargs:", dec_kwargs)
print("Function kwargs:", kwargs)
output = f(*args,**kwargs)
print("After f(*args,**kwargs)")
return output
print("Done wrapping, returning wrapped function")
return wrapped_f
print("Returning wrapping function")
return wrap
def decorator_replacing_named_arg(arg_name):
def wrap(f):
print("Wrapping function...")
def wrapped_f(*args, **kwargs):
print("Inside wrapped function")
print("Function args:", args)
print("Function kwargs:", kwargs)
try: # check if variable name was specified as a keyword arg
named_arg = kwargs[arg_name]
# Apply any transformation to the named arg
# BEGIN
named_arg = 0
# END
kwargs[arg_name] = named_arg
except KeyError: # find the position in which the named arg is expected
try:
arg_pos = f.__code__.co_varnames.index(arg_name)
args = list(args) # make args mutable
named_arg = args[arg_pos] # assign the named arg to a variable
# Apply any transformation to the named arg
# BEGIN
named_arg = 0
# END
args[arg_pos] = named_arg # insert it back in the arg list
args = tuple(args) # convert args back to a tuple
except ValueError:
pass
except IndexError:
pass
output = f(*args,**kwargs)
print("After f(*args,**kwargs)")
return output
print("Done wrapping, returning wrapped function")
return wrapped_f
print("Returning wrapping function")
return wrap
## Specific examples
def _apply_function_to_named_arg(f, arg_name):
# Executed when decorating the function
# called first, when intercept(arg_names) is executed
# returns the actual decorator which, in the second step, will receive the function as the only argument
def decorator(func):
# Executed when decorating the function
# called second, this is the actual decorator
# Try to access the arguments which were originally passed to the function which are expected
# to be in `func._names` if `func` was already decorated
names = getattr(func,'_names',None)
# If `func` was not decorated before, save the input arg names in variable names
if names is None:
code = func.__code__
names = code.co_varnames[:code.co_argcount]
@wraps(func)
def decorated(*args,**kwargs):
# Executed on every function call
# Convert both args and kwargs to a kwargs-style dict called all_args
all_args = kwargs.copy()
for n,v in zip(names,args):
all_args[n] = v
# Apply the transformation defined by `f` to the named arg
all_args[arg_name] = f(all_args[arg_name])
# Once the arguments have been overwritten, call the function
return func(**all_args)
decorated._names = names # store the attributes for subsequent decorations
return decorated
return decorator
def negate_var(arg_name):
return _apply_function_to_named_arg(lambda x: -x, arg_name)
@negate_var('a')
@negate_var('b')
def foo(a,b=0):
return a+b
foo(3,2)
def noblink(func):
def inner(*args, **kwargs):
first_call = not(hasattr(inner, 'args')) or not(hasattr(inner, 'kwargs'))
if not(first_call):
new_args = not(isequal(args,inner.args) and isequal(kwargs,inner.kwargs))
if first_call or new_args:
#Evaluate function
output = func(*args, **kwargs)
# Save input arguments and output
inner.args = args
inner.kwargs = kwargs
inner.output = output
return inner.output
def isequal(a, b, tol=0):
return a == b
return inner