I have the following decorator(as per https://realpython.com/primer-on-python-decorators/#decorators-with-arguments):
def slow_down(_func=None, rate = 1):
def decorator(func):
print(_func) # Added for understanding?
#functools.wraps(func)
def wrapper(*args, **kwargs):
time.sleep(rate)
return func(*args, **kwargs)
return wrapper
if _func==None:
return decorator
else: return decorator(_func)
My question is, shouldn't a this slow_down function initialize with _func = None ? However, when I create a function with the decorator, I get an initialized _func value:
#slow_down
def countdown(time = 5):
print(time)
if time == 0: pass
else: countdown(time-1)
<function countdown at 0x7f1aa0a8da60>
But when I initialize the value of the function, I get None:
#slow_down(rate=2)
def countdown(time = 5):
print(time)
if time == 0: pass
else: countdown(time-1)
None
Shouldn't it be the other way around?
When you use a bare name for a decorator (as in #slow_down), it is called with the function being decorated as a parameter. When parentheses are involved (as in the second case), the decorator is called as written, and is expected to return another function which gets called with the function being decorated as a parameter. The if in your particular decorator allows it to work either way. – jasonharper
Related
Say I have two functions:
def a(name = None):
return "Hello " + name
def b(say = "something"):
return "I will say " + say
What the functions do are mostly irrelevant right now. I would just like to know if there is a way to implement a repeat() function, which will repeat either a() or b() depending on which was last executed.
Thank you in advance!
You can achieve that using decorators
latest = None
latest_args = None
latest_kwargs = None
def my_decorator():
def decorate(func):
def wrapper(*args, **kwargs):
global latest
global latest_args
global latest_kwargs
latest = func
latest_args = args
latest_kwargs = kwargs
return func(*args, **kwargs)
return wrapper
return decorate
def repeat():
if latest is None:
raise Exception("cannot call repeat without calling a function first")
return latest(*latest_args, **latest_kwargs)
#my_decorator()
def a(name = None):
return "Hello " + name
#my_decorator()
def b(say = "something"):
return "I will say " + say
print(a("Bob"))
print(repeat())
print(b())
print(repeat())
Note that this may not be the best way to do so, it's just the first thing that come on to my mind, essentially, every time you call a decorated function, wrapper() will be called instead, which will save the function and the arguments, and then, wrapper() calls the function.
I am learning decorator functions in python and I am wrapping my head around the # syntax.
Here is a simple example of a decorator function which calls the relevant function twice.
def duplicator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
If I understand correctly, it appears that:
#duplicator
def print_hi():
print('We will do this twice')
Is equivalent to:
print_hi = duplicator(print_hi)
print_hi()
However, let's consider if I move to a more complex example. E.g. instead of calling the function twice, I want to call it a user-defined amount of times.
Using an example from here: https://realpython.com/primer-on-python-decorators/
def repeat(num_times):
def decorator_repeat(func):
#functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
I can call this via:
#repeat(num_times=4)
def print_hi(num_times):
print(f"We will do this {num_times} times")
However, this is most certainly not equivalent to:
print_hi = repeat(print_hi)
Because we have the extra argument of num_times.
What am I misunderstanding?
Is it equivalent to:
print_hi = repeat(print_hi, num_times=4)
For the case of the repeat decorator, the equivalent is:
print_hi = repeat(num_times=4)(print_hi)
Here, repeat takes a num_times argument and returns the decorator_repeat closure, which itself takes a func arguments and returns the wrapper_repeat closure.
repeat(num_times) returns a function and that function is used to decorate print_hi.
#repeat(num_times=4)
def print_hi(num_times):
...
amounts to
f = repeat(num_times)
print_hi = f(print_hi)
The function that repeat returns is decorator_repeat, which decorates print_hi.
I'm looking to create a global toggle variable to turn on and off my decoration depending on the argument passed via the command line.
In the case below, Instead of commenting out the #time_md5_comparison when not needed, I want a global toggle depending on the argument passed.
main.py
from timing_decorator import time_md5_comparison
#time_md5_comparison
def md5_comparison(a, b):
if a==b:
return True
else:
return False
timing_decorator.py
def time_md5_comparison(function):
#wraps(function)
def wrapper(*args, **kwargs):
t1 = time.time()
result = function(*args, **kwargs)
t2 = time.time()
print(str(function.__name__) + " " + str("%.6f "%(t2 - t1)))
return result
return wrapper
Can I create a variable, say USE_DECORATOR = True in the main.py, so that the decorator is called and if it's later set to False, the original function is called?
Decorators affect the byte-code generated when the function definition following them is executed (which is before and separate from when the compiled function itself runs when it's called). So, short of reloading the whole module with the decorated function in it, probably the only viable approach would be to make the wrapped function execute differently based on the current setting of the flag variable.
Note the toggle variable had to be put in a mutable container—a list—so that the decorated function would refer to its current value rather than what it was when originally decorated.
main.py
from timing_decorator import time_md5_comparison
USE_DECORATOR = [False]
#time_md5_comparison(USE_DECORATOR)
def md5_comparison(a, b):
if a==b:
return True
else:
return False
md5_comparison(3, 4) # prints nothing
USE_DECORATOR[0] = True
md5_comparison(5, 6) # prints timing info
Decorators which take arguments other than a single function are essentially decorator factories which must create and return the actual decorator used. This is why the decorator in your question needed to be nested one level deeper.
timing_decorator.py
from functools import wraps
import time
def time_md5_comparison(disabled):
def decorator(function):
#wraps(function)
def wrapped(*args, **kwargs):
if disabled[0]:
result = function(*args, **kwargs)
else:
t1 = time.time()
result = function(*args, **kwargs)
t2 = time.time()
print(str(function.__name__)+" "+ str("%.6f " %(t2 - t1)))
return result
return wrapped
return decorator
Yes. You can pass the value in, or simply reset the decorator.
Resetting the decorator would be something like this:
import timing_module
if NO_USE_DECORATOR:
mydecorator = timing_module.empty_decorator
else:
mydecorator = timing_module.time_md5_comparison
#mydecorator
def myfunc(args):
pass
Obviously, you don't have to call it mydecorator. You can reset the time_md5_comparison name instead, to point to whatever you like.
Passing the value as an argument puts the logic in the decorator, which is cleaner:
#module: timing_module
def original_time_md5_comparison(fn):
"""Original code of your decorator here"""
# ...
pass
def no_decorator(fn):
"""Decorator no-op. Just use original function."""
return fn
def time_md5_comparison(use_decorator = True):
if use_decorator:
return original_time_md5_comparison
else:
return no_decorator
For instanse, I have the following code snippet:
def func1(num):
print(num)
def func2(num):
func1(num)
def func3(num):
func2(num)
func1(num)
def begin():
pass
def print_graph():
pass
def main():
begin()
func3(3)
print_graph()
Is there any simple way to print something like that:
func3(1)
func2(1)
func1(1)
func1(1)
I believe, that I have to use globals(), but I don't know, what I do next. It is some sort of study task, therefore I cant use any libraries.
I can go one better than #jme. Here's a version of his decorator that indents and dedents according to your location in the call stack:
import functools
# a factory for decorators
def create_tracer(tab_width):
indentation_level = 0
def decorator(f): # a decorator is a function which takes a function and returns a function
#functools.wraps(f)
def wrapper(*args): # we wish to extend the function that was passed to the decorator, so we define a wrapper function to return
nonlocal indentation_level # python 3 only, sorry
msg = " " * indentation_level + "{}({})".format(f.__name__, ", ".join([str(a) for a in args]))
print(msg)
indentation_level += tab_width # mutate the closure so the next function that is called gets a deeper indentation level
result = f(*args)
indentation_level -= tab_width
return result
return wrapper
return decorator
tracer = create_tracer(4) # create the decorator itself
#tracer
def f1():
x = f2(5)
return f3(x)
#tracer
def f2(x):
return f3(2)*x
#tracer
def f3(x):
return 4*x
f1()
Output:
f1()
f2(5)
f3(2)
f3(40)
The nonlocal statement allows us to mutate the indentation_level in the outer scope. Upon entering a function, we increase the indentation level so that the next print gets indented further. Then upon exiting we decrease it again.
This is called decorator syntax. It's purely 'syntactic sugar'; the transformation into equivalent code without # is very simple.
#d
def f():
pass
is just the same as:
def f():
pass
f = d(f)
As you can see, # simply uses the decorator to process the decorated function in some way, and replaces the original function with the result, just like in #jme's answer. It's like Invasion of the Body Snatchers; we are replacing f with something that looks similar to f but behaves differently.
If you're stuck on Python 2, you can simulate the nonlocal statement by using a class with an instance variable. This might make a bit more sense to you, if you've never used decorators before.
# a class which acts like a decorator
class Tracer(object):
def __init__(self, tab_width):
self.tab_width = tab_width
self.indentation_level = 0
# make the class act like a function (which takes a function and returns a function)
def __call__(self, f):
#functools.wraps(f)
def wrapper(*args):
msg = " " * self.indentation_level + "{}({})".format(f.__name__, ", ".join([str(a) for a in args]))
print msg
self.indentation_level += self.tab_width
result = f(*args)
self.indentation_level -= self.tab_width
return result
return wrapper
tracer = Tracer(4)
#tracer
def f1():
# etc, as above
You mentioned that you're not allowed to change the existing functions. You can retro-fit the decorator by messing around with globals() (though this generally isn't a good idea unless you really need to do it):
for name, val in globals().items(): # use iteritems() in Python 2
if name.contains('f'): # look for the functions we wish to trace
wrapped_func = tracer(val)
globals()[name] = wrapped_func # overwrite the function with our wrapped version
If you don't have access to the source of the module in question, you can achieve something very similar by inspecting the imported module and mutating the items it exports.
The sky's the limit with this approach. You could build this into an industrial-strength code analysis tool by storing the calls in some sort of graph data structure, instead of simply indenting and printing. You could then query your data to answer questions like "which functions in this module are called the most?" or "which functions are the slowest?". In fact, that's a great idea for a library...
If you don't want to use modify code, you can always use sys.settrace. Here is a simple sample:
import sys
import inspect
class Tracer(object):
def __init__(self):
self._indentation_level = 0
#property
def indentation_level(self):
return self._indentation_level
#indentation_level.setter
def indentation_level(self, value):
self._indentation_level = max(0, value)
def __enter__(self):
sys.settrace(self)
def __exit__(self, exc_type, exc_value, traceback):
sys.settrace(None)
def __call__(self, frame, event, args):
frameinfo = inspect.getframeinfo(frame)
filename = frameinfo.filename
# Use `in` instead of comparing because you need to cover for `.pyc` files as well.
if filename in __file__:
return None
if event == 'return':
self.indentation_level -= 1
elif event == 'call':
print "{}{}{}".format(" " * self.indentation_level,
frameinfo.function,
inspect.formatargvalues(*inspect.getargvalues(frame)))
self.indentation_level += 1
else:
return None
return self
Usage:
from tracer import Tracer
def func1(num):
pass
def func2(num):
func1(num)
def func3(num):
func2(num)
func1(num)
def main():
with Tracer():
func3(1)
And results:
func3(num=1)
func2(num=1)
func1(num=1)
func1(num=1)
How about using decorators to print a function's name when it is called? Something like this:
from functools import wraps
def print_on_entry(fn):
#wraps(fn)
def wrapper(*args):
print "{}({})".format(fn.func_name, ", ".join(str(a) for a in args))
fn(*args)
return wrapper
Then you can wrap each of your functions up:
func1 = print_on_entry(func1)
func2 = print_on_entry(func2)
func3 = print_on_entry(func3)
So that:
>>> func3(1)
func3(1)
func2(1)
func1(1)
1
func1(1)
1
Of course there are a lot of assumptions in the above code -- the arguments can be converted to strings, for example -- but you get the picture.
I have some test code:
def num(num):
def deco(func):
def wrap(*args, **kwargs):
inputed_num = num
return func(*args, **kwargs)
return wrap
return deco
#num(5)
def test(a):
return a + inputed_num
print test(1)
when run this code, I got an error shows that 'inputed_num' is not defined
My question is:
In wrap function, is there not a closure that func can got 'inputed_num' ?
Anyway, If not, how should I do to got my aim: Initialize some value, and use this value directly in the main function.
Thinks.
No, there isn't a closure like that. Functions can close over variables that are present in the surrounding lexical context, not in the calling context. In other words, if you actually write one function in another, then the inner one can have access to variables in the outer one:
def f():
g = 2
def f2():
print g
f2()
But functions never have access to variables inside the function that called them.
In general there isn't a way to do what you want, viz., set an arbitrary variable in a function from outside the function. The closest thing is you could use a global inputed_num in your decorator to assign inputed_num as a global variable. Then test would access the global value.
def num(num):
def deco(func):
def wrap(*args, **kwargs):
global outsider
outsider = num
return func(*args, **kwargs)
return wrap
return deco
#num(5)
def test(a):
print a+outsider
>>> test(2)
7
But of course the variable setting is then global, so multiple concurrent uses (e.g., recursion) wouldn't work. (Just for fun, you can also see here for a very arcane way to do this, but it is way too crazy to be useful in a real-world context.)
My question is: In wrap function, is there not a closure that func can got 'inputed_num' ?
Sorry, that's not the way decorators work. They get applied after the function is initially defined. By then, it's too late.
When you write:
#num(5)
def test(a):
return a + inputed_num
That is the equivalent of:
def test(a):
return a + inputed_num
test = num(5)(test) # note that num(5) is called after test() is defined.
To achieve your goal, let inputed_num be the first argument to test. Then, have your decorator pass in that argument:
def num(num):
def deco(func):
def wrap(*args, **kwargs):
inputed_num = num
return func(inputed_num, *args, **kwargs) # this line changed
return wrap
return deco
#num(5)
def test(inputed_num, a): # this line changed
return a + inputed_num
#num(6)
def test2(inputed_num, a):
return a + inputed_num
print test(10) # outputs 15
print test2(10) # outputs 16
Hope that clears everything up for you :-)
As #Raymond puts it - decorators are applied after the function is defined. Which means that while compiling the function body itself, Pythn sees the inputed_num variable, and as i it snod a localy defined variable, it generates code to try access it a as a global variable instead.
Which means you can make a work-around for it in your decorator:
Your decorator can set a global variable with the desired name in the function globals() space, and then call the function. It should work reliably in single-threaded code:
def num(num):
def deco(func):
def wrap(*args, **kwargs):
glob = func.func_globals
marker = object()
original_value = glob.get("inputed_num", marker)
glob["inputed_num"] = num
result = func(*args, **kwargs)
if original_value is marker:
del glob["inputed_num"]
else:
glob["inputed_num"] = original_value
return result
return wrap
return deco
#num(5)
def test(a):
return a + inputed_num
and:
>>> print test(1)
6
It's not the way decorator should be used, I think your purpose may be done with this
def outwrapper(n):
def wrapper(func):
def f(*args, **argw):
return n + func(*args, **argw)
return f
return wrapper
#outwrapper(4)
def test(n):
return n
print test(1)