使用Python装饰器对结果取模时遭遇命名空间冲突问题求助
你的装饰器冲突问题:curry实现的状态共享bug与修复方案
嘿,这个问题我一眼就看出来了——不是modulo的命名空间冲突,而是你自己写的curry装饰器有个致命的状态共享bug!
问题根源分析
你写的curry装饰器里,f_args和f_kwargs是定义在curry函数作用域里的变量,被所有生成的wrapper闭包共享。这意味着:
- 第一次在
_fib里用@modulo(mod)时,curry已经收集了一组参数并执行完成; - 当你在
_luc里再次调用@modulo(mod)时,它会把新的参数追加到之前的参数列表中,导致参数总数超过了modulo需要的2个参数(mod和f); - 最终返回的不是你期望的包装后的
next_luc函数,而是curry的wrapper函数本身!当iterate尝试调用这个wrapper时,它会把pair当作参数传入,而这个wrapper的逻辑是继续收集参数,不是处理pair,自然就抛出了cannot unpack non-iterable function object的错误。
解决方案(三种可选,按推荐度排序)
方案1:把modulo改成带参数的装饰器(最简洁可靠)
根本不需要依赖那个有bug的curry!直接把modulo写成支持参数的装饰器,写法和你原来的调用方式完全一致:
import functools as fun def modulo(mod): # 先接收mod参数 def decorator(f): # 再接收被装饰的函数 if mod: @fun.wraps(f) def wrapper(*args, **kw): result = f(*args, **kw) return tuple(r % mod for r in result) return wrapper else: return f return decorator
这样每次用@modulo(mod)时,都会生成一个独立的decorator闭包,每个闭包里的mod都是独立的,完全不会有状态共享的问题。你的_fib和_luc函数不需要做任何修改,直接就能正常运行。
方案2:修复curry装饰器的状态共享问题
如果你非要保留curry的写法,那必须让每次部分调用都拥有独立的参数状态,不能共享f_args和f_kwargs。重新实现curry:
import functools as fun def curry(f): argc = f.__code__.co_argcount @fun.wraps(f) def wrapper(*args, **kwargs): # 每次调用都创建当前的参数集合,不共享 current_args = list(args) current_kwargs = dict(kwargs) total_args = len(current_args) + len(current_kwargs) if total_args >= argc: return f(*current_args, **current_kwargs) else: # 返回新的wrapper,携带当前已收集的参数 @fun.wraps(f) def new_wrapper(*more_args, **more_kwargs): return wrapper(*(current_args + list(more_args)), **{**current_kwargs, **more_kwargs}) return new_wrapper return wrapper
这个版本的curry每次部分调用都会生成一个新的闭包,每个闭包有自己的参数集合,不会和其他调用冲突。
方案3:用functools.partial替代curry
Python标准库的functools.partial是经过严格测试的参数绑定工具,完全可以替代你自己写的curry:
import functools as fun # 去掉modulo的@curry装饰器 def modulo(mod, f): if mod: @fun.wraps(f) def wrapper(*args, **kw): result = f(*args, **kw) return tuple(r % mod for r in result) return wrapper else: return f # 在生成器里这样使用 def _fib(p=1,q=-1,mod=None): ''' Fibonacci sequence ''' def next_fib(pair): x,y = pair return y, p*x - q*y # 用partial绑定mod参数,再传入函数 next_fib = fun.partial(modulo, mod)(next_fib) yield from ( y for x,y in iterate(next_fib,(1,0)) ) def _luc(p=1,q=-1,mod=None): ''' Lucas sequence ''' def next_luc(pair): x,y = pair return y, p*x - q*y next_luc = fun.partial(modulo, mod)(next_luc) yield from ( y for x,y in iterate(next_luc,(p-2,2)) )
验证效果
不管用哪种方案,现在同时调用两个生成器都能正常工作:
>>> F = _fib() >>> print(take(10,F)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] >>> L = _luc() >>> print(take(10,L)) [2, 1, 3, 4, 7, 11, 18, 29, 47, 76]
内容的提问来源于stack exchange,提问作者ZMoltion
相关产品推荐
相关产品推荐

