【问题标题】:While loop termination in PythonPython中的while循环终止
【发布时间】:2013-07-02 01:44:56
【问题描述】:

我有下面的代码,由于某种原因,while 循环没有终止。

def iterator(i, f):
    print len(f)
    print i
    while i < len(f):
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function()
    return f

打印语句并不是真正需要的,但是计数器i 被另一个函数增加了,所以我想确保它不是问题所在。对于包含 4 个项目的列表 f,它会打印如下:

4, 0, Restarted.
4, 1, iterate.
4, 2, iterate.
4, 3, iterate.
4, 4, iterate.
4, 4, iterate.
etc..

我不明白为什么当i = 4len(f) = 4 时它一直进入while 循环。它应该打破循环并执行返回函数,但由于某种原因它没有。

谁能解释为什么当条件变为假时while循环不会终止?

编辑:一些代码可以更好地解释正在发生的事情。我还澄清了ifunction() 更改,然后调用iterator 并增加i。 希望这是有道理的。

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    while i < len(f):
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function(i, f)
    return f


def function(i, f):
    i += 1
    iterator(i, f)

iterator(i,f)

这样的结果是这样的:

0, 0, Restarted.
1, 1, iterate.
2, 2, iterate.
3, 3, iterate.
4, 3, iterate.
4, 3, iterate.
etc.

【问题讨论】:

  • function() 是否调用 iterator()
  • 请发布function 的代码,或至少发布相关部分,以便我们运行代码并重现您的结果。
  • @HenryKeiter;完成了,希望现在更清楚了。
  • @Mythio 很清楚:您正在混合递归和循环。

标签: python python-2.7 while-loop


【解决方案1】:

计数器 i 被另一个函数增加

不可能。它是一个局部整数。打印的值是 不同 变量的值。尝试在循环中添加print i,你会看到。

int 是一个不可变类型。这意味着这种类型的对象不能改变它的值。唯一可以改变的是某个变量所持有的对象。

def inc(i):
    i+=1

i=0
inc(i)
print i

输出:

0

这是为什么呢?因为i 内部inci 外部它是两个自变量。 inc 中的 i+=1 仅表示“让局部变量 i 现在指向一个新对象 2”。它不会以任何方式影响全局i。就像 C 和 Java(以及大多数其他主要语言中的默认设置)一样,变量按值传递

如果functionf 中删除元素,则此循环可以结束的唯一方法,假设f 是一个列表,因此是可变的。


这是您的新版本的等效代码。请注意,您使用的是递归,而不仅仅是循环。你很快就会出栈:

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    while i < len(f):
        '''if the recursion is less than 4 levels deep: loop forever
           else: don't loop at all'''
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        iterator(i+1, f)
    return f

输出:

0, 0, Restarted. # 0 level recursion, 1st iteration
1, 1, iterate.   # 1 level recursion, 1st iteration
2, 2, iterate.   # 2 level recursion, 1st iteration
3, 3, iterate.   # 3 level recursion, 1st iteration
4, 3, iterate.   # 3 level recursion, 2nd iteration, the condition now is false
4, 3, iterate.   # 3 level recursion, 3rd iteration  
4, 3, iterate.   # 3 level recursion, 4th iteration
4, 3, iterate.   # 3 level recursion, 5th iteration 

等等。

【讨论】:

  • 什么意思?每次调用函数时都会将值 i 与列表 f 一起提供给函数。条件下怎么会是不一样的i
  • 但是i 的值正在改变..第一次打印给出i = 0,1,2,3,4,如果我把print i 放在里面,而它给出i = 0,1,2,3...应该是正确的,因为当i = 4 时,它不应该在一段时间内打印它。
  • @Mythio 将代码与您正在谈论的打印放在一起。无法以这种方式更改局部变量。
  • @Mythio 你只是通过多次调用function() 多次调用iterator() 函数。
  • @Mythio 哦,还有,你正在等待function()iterator() 返回并等待iterator()function() 返回。
【解决方案2】:

您的问题是您没有正确理解全局变量和局部变量。名称i 意味着循环外与循环内的不同。试试这个看看我的意思:

def iterator(i, f):
    # Your code

def function(i, f):
    # Your code

iterator(0, [0, 1, 2, 3])

当然,即使没有全局定义 i,它仍然会运行。那是因为你有名为i 的函数参数,使i 在你的函数中具有新的含义。因为数字是不可变的,所以无论何时您在函数内部“更改”i,您实际上只是在说“好吧,现在 i 将指向该函数内部的不同数字”。您不会更改基础数字。

有两种方法可以解决您的问题。快速而肮脏的方法是从两个函数中删除参数。这将使i 只引用全局变量i

i = 0
f = range(4)
def iterator():
    # etc
def function():
    # etc
iterator()

不鼓励使用像这样的全局变量,因为它不干净,难以调试,并且其他函数可能会抓住它并导致意外行为......通常不是一个好主意。相反,您可以保留函数签名,但使用返回值来做任何您想做的事情。像这样的:

global_f = [0,1,2,3]
global_i = 0

def iterator(i, f):
    while i < len(f):
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        i = function(i, f) # Reassign i or it won't be changed!!
    return f

def function(i, f):
    return i + 1

iterator(global_i, global_f)

请注意,我不确定function应该做什么,所以我将其简化为一个简单的返回语句。当两个函数通常相互调用时要小心;递归很容易陷入无限循环!

【讨论】:

    【解决方案3】:

    本地与全局

    正如其他人所提到的,您将每个名为 i 的变量都视为同一个变量,但事实并非如此。如果您从顶部删除f =i = 行并在底部调用iterator(0, [0,1,2,3]),您将得到相同的结果。事实上,你给这些变量取什么名字并不重要。

    看:

    >>> i = 1
    >>> def inc(i):
    ...     i += 1
    ... 
    >>> inc(i)
    >>> i
    1
    >>> k = 0
    >>> inc(k)
    >>> k
    0
    
    1. 我为变量使用什么名称并不重要。无论您作为参数传递什么,本地都称为i
    2. inc() 中的i 只是为传递给它的对象 ID 提供了一个本地名称。只要您的程序位于该函数的主体内,它的作用域就会持续存在。如果您不返回该变量,它会在您的函数返回时消失。

    所以您可能想知道是否存在全局范围之类的东西。是的,确实如此:如果你在函数内部引用一个变量而不传递它,你会得到一个 NameError - 除非它在函数上方有作用域,像这样:

    >>> n = 10
    >>> def inc(i):
    ...     i += 1
    ...     print n
    ... 
    >>> inc(i)
    10
    >>> i
    1
    

    现在,这并不是说您可以对这个全局变量做任何事情。以下将不起作用:

    >>> def inc(i):
    ...     i += 1
    ...     n += 1
    ... 
    >>> inc(i)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in inc
    UnboundLocalError: local variable 'n' referenced before assignment
    

    要修改函数内部的全局变量,您需要先declare it as global

    >>> def inc(i):
    ...     i += 1
    ...     global n
    ...     n += 1
    ... 
    >>> inc(i)
    >>> n
    11
    

    你实际传递给函数的内容

    为了让事情变得更加复杂,基本上 call by value 在将整数参数传递给 Python 时。您实际上是在传递对整数对象的引用,但这些对象是静态的:

    >>> id(1)
    140272990224888
    >>> id(2)
    140272990224864
    >>> i = 1
    >>> id(i)
    140272990224888
    >>> i += 1
    >>> id(i)
    140272990224864
    >>> id(1)
    140272990224888
    

    当我们增加 i 时,它的 id 发生了变化。 1 的整数对象的 id 没有改变,i 的 id 改变了。这意味着 i 只是一个指向整数对象的指针的名称,当您增加 i 时,您只需将其指针更改为指向值比整数对象的值大 1 的整数对象它先前指向的位置。

    因此,即使您传递对象引用,您也只是传递整数对象 01234 的 ID。而且,由于您不返回这些引用,或者全局声明它们,因此当您的函数返回时它们会失去作用域。

    等待return

    您的另一个疏忽是您假设当iterator() 返回时,程序结束。当您从iterator() 中的while 循环内部调用function() 时,您正在等待function() 返回,然后再继续循环。 function() 然后调用iterator() 然后调用function() 然后调用......你明白了。当您完成带有 4, 3, iterate. 废话返回的最终循环时,您会看到自己再次进入 3, 3, iterate. 循环 - 但 那个 循环永远不会返回。

    当你从一个函数内部调用另一个函数时,你必须等待另一个函数返回才能继续下一条语句。

    >>> def foo():
    ...     bar()
    ...     print "world!"
    ... 
    >>> def bar():
    ...     print "hello"
    ... 
    >>> foo()
    hello
    world!
    

    foo() 无法打印 "world!",直到 bar() 返回。如果你改了bar():

    >>> def bar():
    ...     print "hello"
    ...     foo()
    ... 
    >>> foo()
    hello
    hello
    hello
    hello
    hello
    hello
    ...
      File "<stdin>", line 3, in bar
      File "<stdin>", line 2, in foo
      File "<stdin>", line 3, in bar
      File "<stdin>", line 2, in foo
    RuntimeError: maximum recursion depth exceeded
    >>> 
    KeyboardInterrupt
    

    哎呀。两个函数都无法返回,因为它正在等待对另一个的调用,并且每次一个函数调用另一个时都会创建一个新堆栈 - 最终堆栈溢出。

    如果您不了解堆栈的工作原理,那么这不在我(非常冗长)答案的范围内。你需要查一下。

    混合控制结构

    通过两个相互调用的函数,您创建了一个递归控制结构。此外,通过使用 while 循环而不更改循环条件,您创建了一个无限循环。因此,虽然您的递归有一个基本情况(i &gt;= len(f) 因为i &lt; len(f) 是递归情况),但您的无限循环将导致程序一遍又一遍地调用该基本情况。

    究竟发生了什么(调用堆栈)

    • iterator(0, [0,1,2,3]) 呼叫并等待
    • function(0, [0,1,2,3]) 呼叫并等待
    • iterator(1, [0,1,2,3]) 呼叫等待
    • function(1, [0,1,2,3]) 呼叫等待
    • iterator(2, [0,1,2,3]) 呼叫等待
    • function(2, [0,1,2,3]) 呼叫并等待
    • iterator(3, [0,1,2,3]) 呼叫等待
    • function(3, [0,1,2,3]) 呼叫等待
    • iterator(4, [0,1,2,3])返回
    • function(3, [0,1,2,3])返回
    • iterator(3, [0,1,2,3]) 循环,然后调用并等待
    • function(3, [0,1,2,3])

    这就是为什么你会看到4, 3, iterate 一遍又一遍地发生:你在iterator(4, [0,1,2,3]) 中打印4,但不开始循环,所以iterator(3, [0,1,2,3]) 开始循环,然后你打印3, iterate. 然后你回到iterator(4, [0,1,2,3]),再次打印4,以此类推。因为iterator(4, [0,1,2,3]) 返回,所以不会出现堆栈溢出,但仍然会出现无限循环。

    如何解决

    如果你想要递归:

    f = [0,1,2,3]
    i = 0
    
    def iterator(i, f):
        print i
        if i < len(f):
            print i
            if i == 0:
                print "Restarted."
            else:
                print "iterate."
            function(i, f)
        return f
    
    def function(i, f):
        i += 1
        iterator(i, f)
    
    iterator(i,f)
    

    如果你想要迭代:

    f = [0,1,2,3]
    i = 0
    
    def iterator(i, f):
        print i
        while i < len(f):
            print i
            if i == 0:
                print "Restarted."
            else:
                print "iterate."
            i += 1
        return f
    
    iterator(i,f)
    

    或者,如果您将这些变量声明为全局变量,那么更改会持续存在:

    f = [0,1,2,3]
    i = 0
    
    def iterator():
        global i
        global f
        print i
        while i < len(f):
            print i
            if i == 0:
                print "Restarted."
            else:
                print "iterate."
            function()
        return f
    
    def function():
        global i
        i += 1
        iterator()
    
    iterator()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-09
      • 1970-01-01
      • 1970-01-01
      • 2017-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多