【问题标题】:Best way (if any) to hook a generator function挂钩生成器函数的最佳方法(如果有)
【发布时间】:2016-06-20 19:58:53
【问题描述】:

我正在编写一个非常简单的装饰器来给我一些关于函数的基本调试信息。

from functools import wraps
from time import perf_counter

class debug(object):
    def __init__(self, Time=False, Parameters=False, Doc=False):
        self.t = Time
        self.p = Parameters
        self.d = Doc

    def __call__(self, func):
        @wraps(func)
        def run(*args, **kwargs):
            params = ""
            if self.p:
                params = ", ".join(["{}".format(arg) for arg in args] + ["{}={}".format(k, v) for k, v in kwargs.items()])
            print("\n\tDebug output for '{}({})'".format(func.__name__, params))
            if self.d:
                print('\tDocstring: "{}"'.format(func.__doc__))
            if self.t:
                t1 = perf_counter()
            val = func(*args, **kwargs)
            if self.t:
                t2 = perf_counter()
                print("\tTime Taken: {:.3e} seconds".format(t2 - t1))
            print("\tReturn Type: '{}'\n".format(type(val).__name__))
            return val
        return run

这对正常功能来说很好。

@debug(Parameters=True, Time=True, Doc=True)
def foo(i, j=5):
    """Raises i to 2j"""
    for _ in range(j):
        i **= 2
    return i

i = foo(5, j=3)
# Output:
"""
    Debug output for 'foo(5, j=3)'
    Docstring: "Raises i to 2j"
    Time Taken: 1.067e-05 seconds
    Return Type: 'int'
"""

但是,生成器是另一回事。

@debug(Parameters=True, Time=True, Doc=True)
def bar(i, j=2):
    """Infinite iterator of increment j"""
    while True:
        yield i
        i += j

b = bar()  # Output occurs here
next(b) # No output

现在,根据我的编码,这是完全可以预料的,但我想知道如何挂钩 .__next__() 方法,或者最好的方法是什么。

【问题讨论】:

  • 看起来你可以在这个案例中使用 itertools.tee

标签: python python-3.x generator python-decorators


【解决方案1】:

您可以简单地更改您的 __call__ 方法并在生成器作为输入时返回一个生成器(在文件顶部添加 import types):

def __call__(self, f):
    if isinstance(f, types.GeneratorType):
        def run_gen(*args, **kwargs):
            # do pre stuff...
            for _ in f(*argw, **kwargs):
                yield _
            # do post stuff...
        return run_gen
    else:
        def run(*args, **kwargs):
            # do pre stuff...
            r = f(*argw, **kwargs)
            # do post stuff...
            return r
        return run

【讨论】:

    【解决方案2】:

    你不能替换 function.next 因为它是一个只读值。但是你可以做这样的事情(见debug_generator函数):

    从 functools 导入包装 进口检查

    class debug(object):
        def __init__(self, Time=False, Parameters=False, Doc=False):
            self.t = Time
            self.p = Parameters
            self.d = Doc
    
        def __call__(self, func):
            @wraps(func)
            def debug_generator(func):
                for i, x in enumerate(list(func)):
                    # here you add your debug statements
                    print "What you want: step %s" % i
                    yield x
            @wraps(func)
            def run(*args, **kwargs):
                params = ""
                if self.p:
                    params = ", ".join(["{}".format(arg) for arg in args] + ["{}={}".format(k, v) for k, v in kwargs.items()])
                print("\n\tDebug output for '{}({})'".format(func.__name__, params))
                if self.d:
                    print('\tDocstring: "{}"'.format(func.__doc__))
                val = func(*args, **kwargs)
                print("\tReturn Type: '{}'\n".format(type(val).__name__))
    
                if inspect.isgenerator(val):
                    return debug_generator(val)
                return val
            return run
    

    基本上你只是从你想要调试的生成器中获取所有值,然后你再次yield它们,在循环中添加调试语句。

    【讨论】:

      猜你喜欢
      • 2012-02-17
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      • 2011-12-25
      相关资源
      最近更新 更多