【问题标题】:python generator in class类中的python生成器
【发布时间】:2016-05-31 02:18:38
【问题描述】:

我正在从这个thread 学习生成器。这确实是生成器的一个很好的例子。但是我对其中一个示例代码感到困惑。

>>> class Bank(): # let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())  
$100  
>>> print(corner_street_atm.next())  
$100  
>>> print([corner_street_atm.next() for cash in range(5)])  
['$100', '$100', '$100', '$100', '$100']  
>>> hsbc.crisis = True # crisis is coming, no more money!  
>>> print(corner_street_atm.next())  
<type 'exceptions.StopIteration'>   
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs  
>>> print(wall_street_atm.next())  
<type 'exceptions.StopIteration'>  

>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>

>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

hsbc.crisis 重置为 False 时,corner_steet_atm 只能产生 StopIteration。 为什么会发生这种情况。我认为corner_street_atm在危机之后不是空的。

【问题讨论】:

    标签: python class generator


    【解决方案1】:

    问题出在你说的时候

    corner_street_atm = hsbc.create_atm()
    

    你只调用了一次函数,这就是它会被调用的所有时间。因此,让我们继续观察正在发生的事情

      def create_atm(self):
            # Called right away, when you say the above line and only then
            print("Called function") 
    
            # Loops while crisis = False
            while not self.crisis: 
                yield "$100"
    

    现在,我们可以问一个问题,当危机为真时,我们的方法在哪里寻找,我们发现它指向如下所示的位置:

      def create_atm(self):
            # Called right away, when you say the above line and only then
            print("Called function") 
    
            # Loops while crisis = False
            while not self.crisis: 
                yield "$100"
                #<------ Where you function points after you set crisis to false and get the next
    

    好吧,那里什么都没有,所以你得到一个错误。

    您真正要寻找的似乎是 atm 中的无限循环。像这样的:

    class Bank():
        crisis = False
    
        def create_atm(self):
            print("Called function")
            while True:
                if not self.crisis:
                    yield "$100"
                else:
                    yield "$0"
    

    小心这个,作为你最后的电话

    for cash in brand_new_atm:
        print(cash)
    

    将永远循环,因为您目前看不到 atm 中有多少现金(所以只要没有危机,它只会喷出美元钞票。

    【讨论】:

      【解决方案2】:

      一般是因为你在设置hsbc.crisis=True之后调用了corner_street_atm.next()。如果你这样做,你就没有问题:

      hsbc.crisis=False
      
      corner_street_atm = hsbc.create_atm()
      
      hsbc.crisis=True
      
      hsbc.crisis=False
      
      corner_street_atm.next()
      Out[54]: '$100'
      

      原因是corner_street_atm 是一个生成器(iterator) 对象,并且一旦在这种情况下创建的生成器将在每次调用其next() 方法时调用不带参数的o;如果返回值等于哨兵,StopIteration 将被提升,否则将返回值[1]

      问题是一旦你在hsbc.crisis=True 之后调用corner_street_atm.next(),就会引发异常。并且一旦引发异常,就无法再循环该对象,除非您创建一个新对象!

      【讨论】:

        【解决方案3】:

        这里的问题是你的生成器(它是一种迭代器)已经用尽了。一旦用完,迭代器就不应在产生值和提高StopIteration 之间来回切换(换句话说,即使它不是您想要的,您的示例也应按其应有的方式运行)。以下是有关迭代器协议的更多信息:

        https://docs.python.org/3/tutorial/classes.html#iterators

        在这种特殊情况下,生成器停止产生值的原因是,一旦退出 while 循环(当 'crisis' 为 True 时,一旦调用 next 就会发生这种情况),就无法重新进入循环。

        您最可能想要做的是在单独的ATM 对象上使用__iter__ 魔术方法。更多信息在这里:

        https://docs.python.org/3/library/functions.html#iter

        它可能看起来像这样:

        class ATM():
            def __init__(self, owner):
                self.owner = owner
            def __iter__(self):
                yield "$100"
            def __call__(self):
                if self.owner.crisis is True:
                    return 'crisis!'
        
        class Bank(): # let's create a bank, building ATMs
            crisis = False
            def create_atm(self):
                return ATM(self)
        

        你会希望通过这种方式获得一个 atm 迭代器:

        >>> hsbc = Bank() 
        >>> corner_street_atm = hsbc.create_atm() # this way
        >>> corner_street_atm = ATM(hsbc) # or this way
        >>> corner_street_atm_iterator = iter(corner_street_atm, 'crisis!')
        

        最后一行使用iter 函数的可选形式,因为提供了第二个参数。如果corner_street_atm.__call__() 返回提供给iter 的标记值,则第二种形式返回的迭代器引发StopIteration。在这种情况下,哨兵是'crisis!'

        然后像这样遍历它:

        >>> print(corner_street_atm_iterator.next())  
        $100  
        

        注意:如果您不将 iter'crisis!' 哨兵参数一起使用,ATM 对象永远不会用完钱!

        infinite_atm_iterator = iter(ATM(some_bank))
        

        【讨论】:

          猜你喜欢
          • 2016-12-16
          • 2014-03-12
          • 1970-01-01
          • 2013-10-02
          • 2017-07-20
          • 2013-03-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多