I have the following simple code (which represents much larger code):
def dec(data):
def wrap_func(*args, **kwargs):
if not wrap_func.has_run: print(
'\n$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ \'{}\' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*\n'.format(
data.__name__))
print(data(*args, **kwargs))
if not wrap_func.has_run: print(
'\n$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ \'{}\' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*\n'.format(
data.__name__))
wrap_func.has_run = True
# return result
wrap_func.has_run = False
return wrap_func
@dec
def sum(a=1, b=1, times=1):
return (a + b) * times
@dec
def multi(a=2, b=3):
return sum(a, b=0, times=b)
# sum(1, 3)
multi(2,3)
If only the line of sum (1,3) are operating I get (as intended):
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
4
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
If only the line of multi (2,3)
operating I get these annoying 'leftovers' from the summing function:
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
6
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
None
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
So the issue is, if I use a decorator which uses a function/method which has also the same decorator, it prints me the useless data of the internal function
What I wish to see is:
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
6
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
You have multiple decorated functions, and each function is given it's own independent wrap_func
function object. These are independent.
If you must produce only one set of lines around multiple decorated functions calling each other, you'd need to keep a shared stack count; attach this information to the decorator , not the wrapper that the decorator returns:
def dec(f):
def wrapper(*args, **kwargs):
stack_count = getattr(dec, '_stack_count', 0)
if stack_count == 0:
print(f'-- start of decorated functions -- {f.__name__}')
dec._stack_count = stack_count + 1
try:
result = f(*args, **kwargs)
if result: print(result)
finally:
dec._stack_count = stack_count
if stack_count == 0:
print(f'-- end of decorated functions -- {f.__name__}')
# return result
return wrapper
So the first call to the wrapper sets dec._stack_count
to 1, after which any subsequent calls to wrappers will only further increment that number and nothing more is printed. On return, the counter is decremented again (the old, un-incremented value for that stack level is re-used), and only when that value is 0 again do we print again.
Note that I used try...finally
to ensure the stack counter is decremented even if the decorated function raised an exception.
Demo:
>>> @dec
... def sum(a=1, b=1, times=1):
... return (a + b) * times
...
>>> @dec
... def multi(a=2, b=3):
... return sum(a, b=0, times=b)
...
>>> multi(2, 3)
-- start of decorated functions -- multi
6
-- end of decorated functions -- multi
>>> sum(2, 3)
-- start of decorated functions -- sum
5
-- end of decorated functions -- sum
Keeping track of a stack like that is really a context manager type of problem, so I'd further wrap this in such a context manager:
from contextlib import contextmanager
@contextmanager
def print_outer(before, after):
"""Context manager that prints the before and after text only for the outermost call
This is a reentrant context manager, and is not thread-safe.
"""
outer = getattr(print_outer, '_is_outermost', True)
if outer:
print_outer._is_outermost = False
print(before)
try:
yield
finally:
if outer:
print_outer._is_outermost = True
print(after)
then use this context manager in the decorator:
def dec(f):
def wrapper(*args, **kwargs):
banners = (
f'-- start of decorated functions -- {f.__name__}',
f'-- end of decorated functions -- {f.__name__}'
)
with print_outer(*banners):
result = f(*args, **kwargs)
if result: print(result)
# return result
return wrapper
You can use some kind of global state lock which indicates whether one of the wrappers in the stack has printed already. This lock will be acquired by the outermost wrapper and prevents the inner ones from printing.
Code example:
lock = False
def dec(f):
def wrapper(*args, **kwargs):
global lock
if not lock:
lock = True
print('Start', f.__name__)
result = f(*args, **kwargs)
print('End', f.__name__)
lock = False
else:
result = f(*args, **kwargs)
return result
return wrapper
Alternatively:
lock = False
def dec(f):
def wrapper(*args, **kwargs):
global lock
locked = lock
if not locked:
lock = True
print('Start', f.__name__)
result = f(*args, **kwargs)
if not locked:
lock = False
print('End', f.__name__)
return result
return wrapper
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.