简体   繁体   中英

Re-use incorrect printing

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM