在Python中封装生成器函数

6 投票
2 回答
5847 浏览
提问于 2025-04-16 19:55

我正在写一些代码,用来遍历一个可能有循环引用的结构。为了避免在递归函数开始时进行明确的检查,我想创建一个装饰器,这样同样的参数就不能多次调用这个函数。

下面是我想到的代码。按照现在的写法,它会尝试遍历一个空值(Nonetype),然后抛出一个异常。我知道可以通过返回一个空列表来解决这个问题,但我想做得更优雅一些。有没有办法在装饰器内部判断被装饰的函数是否是一个生成器函数?这样的话,如果是生成器我可以有条件地抛出StopIteration异常,否则就返回空值(None)。

previous = set()
def NO_DUPLICATE_CALLS(func):
    def wrapped(*args, **kwargs):
        if args in previous:
            print 'skipping previous call to %s with args %s %s' % (func.func_name, repr(args), repr(kwargs))
            return
        else:
            ret = func(*args, **kwargs)
            previous.add(args)
            return ret
    return wrapped

@NO_DUPLICATE_CALLS
def foo(x):
    for y in x:
        yield y

for f in foo('Hello'):
    print f

for f in foo('Hello'):
    print f

2 个回答

4

很遗憾,实际上没有一个好的方法可以知道一个函数是否会返回某种可迭代的东西,除非你真的去调用它。你可以看看这个回答,里面对一些可能出现的问题有很好的解释。

不过,你可以通过使用一种改进版的记忆装饰器来解决这个问题。通常,记忆装饰器会为之前的参数创建一个缓存,存储返回值,但你可以选择只存储返回值的类型,而不是完整的值。当你遇到之前见过的参数时,就返回该类型的新初始化值,这样就会得到一个空字符串、空列表等等。

这里有一个关于记忆装饰器的链接,可以帮助你入门:
http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

5

好的,看看这个:

>>> from inspect import isgeneratorfunction
>>> def foo(x):
...    for y in x:
...        yield y
...
>>> isgeneratorfunction(foo)
True

不过,这个需要Python 2.6或更高版本。

撰写回答