重复使用不正确的打印

时间:2018-04-29 10:35:24

标签: python decorator

我有以下简单代码(代表更大的代码):

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)

如果只有总和(1,3)行正在运行,我得到(按预期):

$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*
4
$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'sum' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*

如果仅multi (2,3)操作的行我从求和函数中得到这些烦人的'剩余':

$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___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' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*

所以问题是,如果我使用的装饰器使用的函数/方法也有相同的装饰器,它会打印出内部函数的无用数据

我希望看到的是:

$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is Start of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*

6

$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*___This is End of FUNC___ 'multi' $*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*$*

2 个答案:

答案 0 :(得分:2)

你有多个修饰函数,每个函数都有自己独立的wrap_func函数对象。这些是独立的。

如果必须围绕多个相互调用的函数生成一组线,则需要保持共享堆栈数;将此信息附加到装饰器,而不是装饰器返回的包装器:

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

因此,对包装器的第一次调用将dec._stack_count设置为1,之后对包装器的任何后续调用将仅进一步增加该数字,并且不再打印任何内容。返回时,计数器再次递减(重新使用该堆栈级别的旧的,未递增的值),并且只有当该值再次为0时,我们才会再次打印。

请注意,我使用try...finally来确保堆栈计数器递减,即使装饰函数引发了异常。

演示:

>>> @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

跟踪这样的堆栈实际上是一个上下文管理器类型的问题,所以我在这样的上下文管理器中进一步包装它:

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)

然后在装饰器中使用此上下文管理器:

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

答案 1 :(得分:1)

您可以使用某种全局状态锁定,指示堆栈中的某个包装器是否已经打印。此锁定将由最外层的包装器获取,并防止内部包装打印。

代码示例:

localStorage

可替换地:

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