one decorator for 3 different functions - python

I am writing python API and I have one problem.
I have 3 different functions:
func1() -> return only text
func2(name) -> return text only but takes parameter
func3(name) -> this function create a file "name".txt
Now I have a problem with decorator, I want to create a log decorator that is called everytime function is called.
Problem is that I dont know how to simply do it, I know how to create it with no param or one param but I have no idea hot to create universal decorator that will work for all three functions.
Now i have something like this:
def log(func):
def wrapper(name):
func(name)
log = ('write something here')
f = open('log.txt', 'a+')
f.write(log + "\n")
f.close(name)
return wrapper

Your wrapper should accept an arbitrary number of arguments, with the *args and **kwargs syntax to capture both positional and keyword arguments. Make sure to return whatever the wrapped function returns:
def log(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
log = ('write something here')
with open('log.txt', 'a+') as f:
f.write(log + "\n")
return result
return wrapper
You probably want to add in the #functools.wraps decorator; this copies across any documentation and other metadata from the original wrapped function to the new wrapper:
from functools import wraps
def log(func):
#wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
log = ('write something here')
with open('log.txt', 'a+') as f:
f.write(log + "\n")
return result
return wrapper
Last but not least, rather than reopening a log file yourself, take a look at the logging module to handle log files for you.

def log(func):
def wrapper(*args, **kwds):
log = func(*args, **kwds)
f = open('log.txt', 'a+')
f.write(log + "\n")
f.close()
return wrapper
#log
def func1():
return "Called function 1"
#log
def func2(name):
return "Called function 2 with " + name
#log
def func3(name):
f = open('name.txt', 'a+')
f.write(name + " from func3\n")
f.close()
return "Called function 3 with " + name
def main():
func1()
func2("func2")
func3("func3")
if __name__ == '__main__':
main()
Log.txt becomes:
Called function 1
Called function 2 with func2
Called function 3 with func3

Related

python decorator takes 1 positional argument but 5 were given after modifying for pytest

I'd appreciate some help with the following code, as I'm still relatively new to Python, and despite countless days trying to figure out where i'm going wrong, i cant seem to spot the error i'm making.
I've adapted the following code from an article on medium to create a logging decorator and then enhanced it to try and "redact pandas df and dictionary" from the logs. Using functools caused me a problem with pytest and pytest fixtures. A post on stack overflow suggested dropping functools in favour of decorators.
def log_decorator(_func=None):
def log_decorator_info(func):
def log_decorator_wrapper(*args, **kwargs):
_logger = Logger()
logger_obj = _logger.get_logger()
args_passed_in_function = args_excl_df_dict(*args)
kwargs_passed_in_function = kwargs_excl_df_dict(**kwargs)
formatted_arguments = join_args_kwargs(args_passed_in_function,kwargs_passed_in_function)
py_file_caller = getframeinfo(stack()[1][0])
extra_args = { 'func_name_override': func.__name__,'file_name_override': os.path.basename(py_file_caller.filename) }
""" Before to the function execution, log function details."""
logger_obj.info(f"Begin function - Arguments: {formatted_arguments}", extra=extra_args)
try:
""" log return value from the function """
args_returned_from_function = args_excl_df_dict(func(*args))
kwargs_returned_from_function = []
formatted_arguments = join_args_kwargs(args_returned_from_function,kwargs_returned_from_function)
logger_obj.info(f"End function - Returned: {formatted_arguments}", extra=extra_args)
except:
"""log exception if occurs in function"""
error_raised = str(sys.exc_info()[1])
logger_obj.error(f"Exception: {str(sys.exc_info()[1])}",extra=extra_args)
msg_to_send = f"{func.__name__} {error_raised}"
send_alert(APP_NAME,msg_to_send,'error')
raise
return func(*args, **kwargs)
return decorator.decorator(log_decorator_wrapper, func)
if _func is None:
return log_decorator_info
else:
return log_decorator_info(_func)
Having adapted the above code i cant figure out what is causing the following error
args_returned_from_function = args_excl_df_dict(func(*args))
TypeError: test_me() takes 4 positional arguments but 5 were given
Other functions which the log decorator relies on
def args_excl_df_dict(*args):
args_list = []
for a in args:
if isinstance(a,(pd.DataFrame,dict)):
a = 'redacted from log'
args_list.append(repr(a))
else:
args_list.append(repr(a))
return args_list
def kwargs_excl_df_dict(**kwargs):
kwargs_list = []
for k, v in kwargs.items():
if isinstance(v,(dict,pd.DataFrame)):
v = 'redacted from log'
kwargs_list.append(f"{k}={v!r}")
else:
kwargs_list.append(f"{k}={v!r}")
return kwargs_list
def join_args_kwargs(args,kwargs):
formatted_arguments = ", ".join(args + kwargs)
return str(formatted_arguments)
This is the code calling the decorator
#log_decorator.log_decorator()
def test_me(a, b, c, d):
return a, b
test_me(string, number, dictionary, pandas_df)
I think the problem is that the wrapper is including the function as an argument to the function.
Try adding this line and see if it helps
args = args[1:]
intor your log_decorator_wrapper function towards the top. Like this.
def log_decorator(_func=None):
def log_decorator_info(func):
def log_decorator_wrapper(*args, **kwargs):
args = args[1:] # < -------------------here
_logger = Logger()
logger_obj = _logger.get_logger()
args_passed_in_function = args_excl_df_dict(*args)
kwargs_passed_in_function = kwargs_excl_df_dict(**kwargs)
formatted_arguments = join_args_kwargs(args_passed_in_function,kwargs_passed_in_function)
py_file_caller = getframeinfo(stack()[1][0])
extra_args = { 'func_name_override': func.__name__,'file_name_override': os.path.basename(py_file_caller.filename) }
""" Before to the function execution, log function details."""
logger_obj.info(f"Begin function - Arguments: {formatted_arguments}", extra=extra_args)
try:
""" log return value from the function """
args_returned_from_function = args_excl_df_dict(func(*args))
kwargs_returned_from_function = []
formatted_arguments = join_args_kwargs(args_returned_from_function,kwargs_returned_from_function)
logger_obj.info(f"End function - Returned: {formatted_arguments}", extra=extra_args)
except:
"""log exception if occurs in function"""
error_raised = str(sys.exc_info()[1])
logger_obj.error(f"Exception: {str(sys.exc_info()[1])}",extra=extra_args)
msg_to_send = f"{func.__name__} {error_raised}"
send_alert(APP_NAME,msg_to_send,'error')
raise
return func(*args, **kwargs)
return decorator.decorator(log_decorator_wrapper, func)
if _func is None:
return log_decorator_info
else:
return log_decorator_info(_func)
If your code is as is in your editor, maybe look at the indentation on the first three functions. Then start from there to move down

How to obtain tuple values from inside of decorator functions in Python

I am exploring decorator functions in Python. My objective is to return a tuple from a wrapper function inside of the decorator function, when the original function that is passed to the decorator functin also returns a tuple. My code snippet is pasted below:
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
s, o = original_function(*args, **kwargs)
return s, o
return wrapper_function
def test_function(name, command):
status = True
output = dict()
output['message'] = command + " " + name
return status, output
decorator_func_var = decorator_function(test_function("Kaushik", "Hello"))
ok, out = decorator_func_var()
print(ok)
print(out)
However, when I execute this, I get an error message as follows:
I am curious to know where I am going wrong with my code snippet and how can I obtain the tuple values when calling a decorated function. I would really appreciate any suggestions or feedback.
Your decorator takes a function as an argument, and returns a function. You just want
ok, out = decorator_function(test_function)("Kaushik", "Hello")
or a little bit more clearly:
wrapped_function = decorator_function(test_function)
ok, out = wrapped_function("Kaushik", "Hello")
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
s, o = original_function(*args, **kwargs)
return s, o
return wrapper_function
def test_function(name, command):
status = True
output = dict()
output['message'] = command + " " + name
return status, output
decorator_func_var = decorator_function(test_function)
ok, out = decorator_func_var("Kaushik", "Hello")
print(ok)
print(out)
You pass the parameters after initialising decorator_func_var

Python 3 - Decorators execution flow

The below example is taken from python cookbook 3rd edition section 9.5.
I placed break points at each line to understand the flow of execution . Below is the code sample, its output and the questions I have . I have tried to explain my question , let me know if you need further info.
from functools import wraps, partial
import logging
# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None):
if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
#wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
#attach_wrapper(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg
return wrapper
return decorate
# Example use
#logged(logging.DEBUG)
def add(x, y):
return x + y
logging.basicConfig(level=logging.DEBUG)
add.set_message('Add called')
#add.set_level(logging.WARNING)
print (add(2, 3))
output is
DEBUG:__main__:Add called
5
I understand the concept of decorators, but this is confusing a little.
scenario 1. When the following line is debugged #logged(logging.DEBUG) , we get
decorate = .decorate at 0x000000000< memoryaddress >>
Question : why would the control go back to execute the function " def decorate" ? Is it because the "decorate" function is on the top of the stack ?
scenario 2 :When executing #attach_wrapper(wrapper) , the control goes to execute attach_wrapper(obj, func=None) and partial function returns
func =
question : why would the control go back to execute def attach_wrapper(obj, func=None):
and how would this time the value for func is *.decorate..set_message at 0x000000000 >
being passed to the attach_wrapper ?
Scenario 1
This:
#logged(logging.DEBUG)
def add(x, y):
....
is the same as this:
def add(x, y):
....
add = logged(logging.DEBUG)(add)
Note that there are two calls there: first logged(logging.DEBUG) returns decorate and then decorate(add) is called.
Scenario 2
Same as in Scenario 1, this:
#attach_wrapper(wrapper)
def set_message(newmsg):
...
is the same as this:
def set_message(newmsg):
...
set_message = attach_wrapper(wrapper)(set_message)
Again, there are two calls: first attach_wrapper(wrapper) returns the partial object and then partial(set_message) is called.
In other words...
logged and attach_wrapper are not decorators. Those are functions which return decorators. That is why two calls are made: one to the function which returns the decorator and another the the decorator itself.

Where should I put my decorators?

I tried to look for a similar question without luck. I'm quite new to python, so, please, be nice :)
I have my class, but I wanted to log when functions are executed and whit which parameters, so I wrote my decorators.
At moment I have everything in a single script, which looks more or less like:
import...
decorators...
my class...
Sincerely I don't like the decorators hanging outside of my class, I have a function to initialize the log level and a function which is never used as decorator, but it is used by the other decorators. [Code at the end of the question]
Should I put my decorators in a decorator.py file and import it in my class script? Should I leave them like that and learn to love this kind of file structure?
def initialize_log(db):
logzero.loglevel(logging.INFO)
logzero.logfile("sw-" + db + ".log")
def _log(log_function, f, *args, **kwargs):
arguments = ""
if len(args) > 1:
arguments = " ({})".format(','.join(map(str, args[1:])))
kwarguments = ""
if len(kwargs) > 0:
kwarguments = " ({})".format(','.join([str(k) + "=" + str(kwargs[k]) for k in kwargs]))
log_function(f.__name__ + " started" + arguments + kwarguments)
res = f(*args, **kwargs)
log_function(f.__name__ + " completed" + arguments + kwarguments)
return res
def log_info(f):
def _decorator(*args, **kwargs):
return _log(logger.info, f, *args, **kwargs)
return _decorator
def log_debug(f):
def _decorator(*args, **kwargs):
return _log(logger.debug, f, *args, **kwargs)
return _decorator

Counting python method calls within another method

I'm actually trying doing this in Java, but I'm in the process of teaching myself python and it made me wonder if there was an easy/clever way to do this with wrappers or something.
I want to know how many times a specific method was called inside another method. For example:
def foo(z):
#do something
return result
def bar(x,y):
#complicated algorithm/logic involving foo
return foobar
So for each call to bar with various parameters, I'd like to know how many times foo was called, perhaps with output like this:
>>> print bar('xyz',3)
foo was called 15 times
[results here]
>>> print bar('stuv',6)
foo was called 23 times
[other results here]
edit: I realize I could just slap a counter inside bar and dump it when I return, but it would be cool if there was some magic you could do with wrappers to accomplish the same thing. It would also mean I could reuse the same wrappers somewhere else without having to modify any code inside the method.
Sounds like almost the textbook example for decorators!
def counted(fn):
def wrapper(*args, **kwargs):
wrapper.called += 1
return fn(*args, **kwargs)
wrapper.called = 0
wrapper.__name__ = fn.__name__
return wrapper
#counted
def foo():
return
>>> foo()
>>> foo.called
1
You could even use another decorator to automate the recording of how many times a function is called inside another function:
def counting(other):
def decorator(fn):
def wrapper(*args, **kwargs):
other.called = 0
try:
return fn(*args, **kwargs)
finally:
print '%s was called %i times' % (other.__name__, other.called)
wrapper.__name__ = fn.__name__
return wrapper
return decorator
#counting(foo)
def bar():
foo()
foo()
>>> bar()
foo was called 2 times
If foo or bar can end up calling themselves, though, you'd need a more complicated solution involving stacks to cope with the recursion. Then you're heading towards a full-on profiler...
Possibly this wrapped decorator stuff, which tends to be used for magic, isn't the ideal place to be looking if you're still ‘teaching yourself Python’!
This defines a decorator to do it:
def count_calls(fn):
def _counting(*args, **kwargs):
_counting.calls += 1
return fn(*args, **kwargs)
_counting.calls = 0
return _counting
#count_calls
def foo(x):
return x
def bar(y):
foo(y)
foo(y)
bar(1)
print foo.calls
After your response - here's a way with a decorator factory...
import inspect
def make_decorators():
# Mutable shared storage...
caller_L = []
callee_L = []
called_count = [0]
def caller_decorator(caller):
caller_L.append(caller)
def counting_caller(*args, **kwargs):
# Returning result here separate from the count report in case
# the result needs to be used...
result = caller(*args, **kwargs)
print callee_L[0].__name__, \
'was called', called_count[0], 'times'
called_count[0] = 0
return result
return counting_caller
def callee_decorator(callee):
callee_L.append(callee)
def counting_callee(*args, **kwargs):
# Next two lines are an alternative to
# sys._getframe(1).f_code.co_name mentioned by Ned...
current_frame = inspect.currentframe()
caller_name = inspect.getouterframes(current_frame)[1][3]
if caller_name == caller_L[0].__name__:
called_count[0] += 1
return callee(*args, **kwargs)
return counting_callee
return caller_decorator, callee_decorator
caller_decorator, callee_decorator = make_decorators()
#callee_decorator
def foo(z):
#do something
return ' foo result'
#caller_decorator
def bar(x,y):
# complicated algorithm/logic simulation...
for i in xrange(x+y):
foo(i)
foobar = 'some result other than the call count that you might use'
return foobar
bar(1,1)
bar(1,2)
bar(2,2)
And here's the output (tested with Python 2.5.2):
foo was called 2 times
foo was called 3 times
foo was called 4 times

Categories

Resources