You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python装饰器对结果取模时遭遇命名空间冲突问题求助

你的装饰器冲突问题:curry实现的状态共享bug与修复方案

嘿,这个问题我一眼就看出来了——不是modulo的命名空间冲突,而是你自己写的curry装饰器有个致命的状态共享bug!

问题根源分析

你写的curry装饰器里,f_args和f_kwargs是定义在curry函数作用域里的变量,被所有生成的wrapper闭包共享。这意味着:

  1. 第一次在_fib里用@modulo(mod)时,curry已经收集了一组参数并执行完成;
  2. 当你在_luc里再次调用@modulo(mod)时,它会把新的参数追加到之前的参数列表中,导致参数总数超过了modulo需要的2个参数(mod和f);
  3. 最终返回的不是你期望的包装后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:47:45