Python方法也可以是生成器函数吗?

2 投票
7 回答
2807 浏览
提问于 2025-04-16 14:24

我正在尝试创建一个方法,它可以像生成器函数一样工作,只需简单地切换一个开关(下面的 want_gen)。

大概是这样的:

def optimize(x, want_gen):
    # ... declaration and validation code
    for i in range(100):
        # estimate foo, bar, baz
        # ... some code here

        x = calculate_next_x(x, foo, bar, baz)

        if want_gen:
           yield x

    if not want_gen:
       return x

但当然,这样是行不通的——Python 显然不允许在同一个方法中同时使用 yieldreturn,尽管它们不可能同时执行。

这段代码比较复杂,重构声明和验证的代码也没有太大意义(状态变量太多了——我最终会得到一些难以命名的辅助函数,参数超过7个,这样看起来很糟糕)。当然,我希望尽量避免代码重复。

有没有什么代码模式可以让我实现我想要的行为呢?


我为什么需要这个?

我有一个相当复杂且耗时的优化过程,我希望在运行时能够获得关于其当前状态的反馈(比如在图形界面上显示)。旧的行为需要保留,以便与之前的版本兼容。多线程和消息传递的工作量太大,带来的好处却不多,尤其是在需要跨平台操作的时候。

编辑:也许我应该提到,由于每个优化步骤都比较耗时(还涉及一些数值模拟),我希望能够在某个迭代步骤中“插入”并调整一些参数,或者干脆中止整个过程。使用生成器似乎是个好主意,因为我可以在需要的时候启动另一个迭代,同时调整一些参数。

7 个回答

3

好吧……我们可以记住,yield这个关键词是为了让生成器对象能够存在而引入的。不过,你也可以选择自己从头实现生成器,或者结合两者的优点来使用。

class  Optimize(object):
    def __init__(self, x):
        self.x = x
    def __iter__(self):
        x = self.x
        # ... declaration and validation code
        for i in range(100):
            # estimate foo, bar, baz
            # ... some code here

            x = calculate_next_x(x, foo, bar, baz)
            yield x
    def __call__(self):
        gen = iter(self)
        return gen.next()

def optimize(x, wantgen):
    if wantgen:
        return iter(Optimize(x))
    else:
        return Optimize(x)()

其实你甚至不需要“优化”这个函数包装器——我只是把它放进去,让它可以直接替代你的例子(看看能否工作)。

根据这个类的声明,你可以简单地这样做:

for y in Optimize(x):
    #code

来把它当作生成器使用,或者:

 k = Optimize(x)()

把它当作普通函数使用。

3

我对我的回答做了个补充,为什么不总是使用“yield”呢?你可以写一个只返回一个值的函数。如果你不想这样,那就可以选择让你的函数返回一个生成器,或者直接返回一个值:

 def stuff(x, want_gen):
     if want_gen:
         def my_gen(x):
             #code with yield
         return my_gen
     else:
         return x

这样你就总是能返回一个值。在Python中,函数本身就是对象。

3

既然你只是想要对一个运行很久的函数得到一些反馈,那为什么不传入一个回调函数的引用呢?这样可以在固定的时间间隔内调用这个回调函数,给你一些进度更新。

撰写回答