python有关生成器函数的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python有关生成器函数的问题相关的知识,希望对你有一定的参考价值。

请回答:为什么图1后面会有“Exception ignored…”?为什么图2就没有?那到底是什么东西?

你使用except将生成器退出的事件拦截了。

这将导致生成器无法释放。

如果需要处理未知错误,应当将StopIteration错误单独except并直接抛出:

def myGenerator():
    value = 1
    while True:
        try:
            yield value
            value += 1
        except StopIteration:
            raise StopIteration
        except:
            value = 1

gen = myGenerator()
print(next(gen))
print(next(gen))

追问

“你使用except将生成器退出的事件拦截了。”这句话能具体解释一下吗?正常来说左图控制流并不会流经except。所以你的意思是每次yield出一个值就是生成器退出事件?

追答

yield时与停止生成器是两码事。

yield时,跳出生成器的代码体,但生成器实例还在。就像楼道灯,你拍一下手,它亮了(生成器里的代码开始执行),过一会它熄了(yield ...),但灯还是在的,还可以用,你可以再拍一下手,它还会亮,别人拍一下手,它也会亮。
而StopIteration错误则是另一回事,还拿楼道灯来比喻,可能因为时间长了,灯泡的灯丝烧断了,或者有小孩子无聊用弹弓把灯泡打破了,这灯就寿终了。你再怎么拍手它都不会亮了。
对于生成器来说,一旦生成器产生了StopIteration错误消息,它就不再返回yield后的表达式,而是返回这个StopIteration错误。这时,如果是用for来迭代,for得到这个StopIteration错误就会结束,而如果使用next()来迭代,则会得到一个明确的StopIteration错误。
而生成器释放在内部机制上也是需要StopIteration状态才可以进行的,它会先将自己置为StopIteration状态然后再进行资源的释放。
你使用except拦截了所有错误,这其中就包括了StopIteration错误,因此生成器就无法将自己置为StopIteration状态,进而无法释放自己,于时,无法释放。这一般发生在生成器实例释放时或者代码将要执行到生成器实例作用域范围之外时。

参考技术A 这个函数用的递归,既然是递归,就要有结束递归的条件
就按你举得例子
第一次是:
x = [[1,2],[3,4],[5,6]]
第二次dd(i)时:
x = [1,2]
第三次:
x = 1
这时,for i in x 显然要报异常追问

文不对题,回答错题目了?

参考技术B next(o()) 每次都创建了一个新的生成器. 改成只创建一次.
def o(): n = 1 while True: n = n + 2 yield n a=o(); print(next(a)) print(next(a)) print(next(a))追问

你确定吗?gen = myGenerator()才是创建生成器,next(gen)是预激生成器吧

参考技术C 在Python3.x的环境,没有while的话在调用next()时就是会引发StopIteration~

Python模板设计模式中有关重写模板方法的问题

我违反了Python中的模板设计模式吗?

因此,我有一个用于创建模板方法的基类,我想创建另外两个可用于执行正常函数返回和生成器函数产生的类?

我不得不重写do_something方法并将语句从“ return”更改为关键字“ yield”,或者我应该做另一种替代设计代码更改?

from abc import ABC, abstractmethod

class BaseClass(ABC):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        return x + y

    @abstractmethod
    def do_step1(self):
        pass

    @abstractmethod
    def do_step2(self):
        pass

class ReturnClass(BaseClass):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        return x + y

    def do_step1(self):
        return 1

    def do_step2(self):
        return 1

class YieldClass(BaseClass):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        yield x + y

    def do_step1(self):
        return 2

    def do_step2(self):
        return 2

class ConcreteReturnClass(ReturnClass):
    def do_step1(self):
        return 3

    def do_step2(self):
        return 3

class ConcreteYieldClass(YieldClass):
    def do_step1(self):
        return 4

    def do_step2(self):
        return 4

if __name__ == '__main__':
    return_class = ConcreteReturnClass();
    print(return_class.do_something())

    yield_class = ConcreteYieldClass();
    print(next(yield_class.do_something()))

我的目标是创建一个基类作为模板,但我想重用同一类,并将实现方式从仅返回一个关键字更改为产生生成器函数的方式。我认为我违反了该原则,但我找不到可行的替代方法。

答案

在类BaseClass中,do_something将是您的模板方法,即,它“根据子类重写以提供具体行为的抽象操作定义算法”。在这种情况下,这些操作由抽象方法do_step1do_step2提供,它们需要被子类覆盖。

但在子类ReturnClass中,您已经覆盖了模板方法do_something本身。尽管不被禁止,但是这并不常见。但是在这种情况下,您提供的实现似乎与基类中的实现相同,因此此操作无法完成。我不知道您为什么说您“必须重写do_something方法。”

x = ReturnBase(): print(x.do_something()) // prints 2

无论是否覆盖2,以上打印do_something都是ReturnClass 如果您提供了相同的定义

但是我看到的真正问题是:类或接口代表合同。对于每种方法,都有隐含的前提条件,必须先执行这些条件才能进行调用。对于实现stack并具有pop操作的类,调用pop之前的先决条件是,堆栈中必须至少包含一个元素才能使操作成功。同样,每个方法调用都有一个隐含的后置条件。在最后一个示例中,承诺的是pop方法将使堆栈中的项比调用前少一个。当子类重写基类方法时,它必须遵守隐含的约定,因为它不需要对方法调用提出更强的先决条件,也不承诺在方法返回时提供更少的条件。

在您的示例中,您有两个子类,它们的do_something方法返回的结果类型完全不同。这是对Liskov substitution principle的根本违反,该声明指出,如果S是T的子类型,则可以用S类型的对象替换T类型的对象。而YieldClass类型的对象可能是T的subclass BaseClass,它不是BaseClasssubtype,因为do_something_1(以及BaseClass中)的方法ReturnClass返回int,而do_something_1中的方法YieldClass返回发电机。类YeldClass违反了其基类的约定,并且是非常差的面向对象设计。

以上是关于python有关生成器函数的问题的主要内容,如果未能解决你的问题,请参考以下文章

Python模板设计模式中有关重写模板方法的问题

python 生成有关PyCon 2014视频的统计信息

python 生成有关PyCon 2014视频的统计信息

源生成器:有关引用项目的信息?

如何在 select 语句中包含 PERCENTILE_CONT 列,而不会生成有关 ORDER BY 子句或聚合函数的错误?

Halcon学习之四:有关图像生成的函数