繁体   English   中英

为什么“动态更改列表”在两个嵌套循环级别中表现不同?

[英]Why “changing list dynamically” behave differently in two level of nested loop?

我的问题是:

为什么它适用于内部循环,而不适用于外部循环?

如何写一些东西来完成这项工作...我知道在循环中动态更改列表不是一个安全的解决方案。


代码1:

list_a = [1,2,3]
list_b = [11,12,13]

for i in list_a:
    for j in list_b:
        print(i, j)

结果1:

1 11
1 12
1 13
2 11
2 12
2 13
3 11
3 12
3 13

Code 1是用于打印两个列表的组合的两级嵌套循环,它可以按预期工作。

我想在循环过程中动态更改列表。 通过更改触发循环的列表,我希望循环行为也可以动态更改。


代码2:

list_a = [1,2,3]
list_b = [11,12,13]

for i in list_a:
    list_a = [100, 200]
    for j in list_b:
        print(i, j)

代码3:

list_a = [1,2,3]
list_b = [11,12,13]

for i in list_a:
    for j in list_b:
        list_a = [100, 200]
        print(i, j)

结果2/3:

1 11
1 12
1 13
2 11
2 12
2 13
3 11
3 12
3 13

Code 2Code 3用于更改第一个列表。 尽管list_a在循环的第一步中已更新,但外部for loop的循环行为不会改变。


代码4:

list_a = [1,2,3]
list_b = [11,12,13]

for i in list_a:
    for j in list_b:
        list_b = [100, 200]
        print(i, j)

结果4:

1 11
1 12
1 13
2 100
2 200
3 100
3 200

Code 4用于更改第二个列表。 list_b在循环的第一步中更新,并且内部循环的行为受到影响。

问题在于for循环会在入口处创建并绑定其迭代器,而不必重新绑定它们以后的名称也没关系。 想象一下这种形式的for循环:

for i in list_a:

像这样实现:

_unnamed_iter_ = iter(list_a)
while True:
    try:
       i = next(_unnamed_iter_)
    except StopIteration:
       break

请注意,在循环期间,它永远不会查看list_a 它从循环开始之前由绑定到list_a绑定的内容的迭代器读取,但是将list_a重新分配给 list ,而不是修改它当前绑定的list这不会更改迭代器的外观。 修改当前绑定的list 将是 “有效的”,尽管它显然违反了规则(在迭代集合的同时修改集合有时是可行的,但通常是一些细微的错误的来源)。 使代码按预期工作的一种简单方法是进行更改:

list_a = [100, 200]

至:

list_a[:] = [100, 200]

切片分配将替换绑定到list_alist内容 ,它不会将其重新绑定到全新的list ,因此原始list支持的迭代器将按预期进行修改。

代码4之所以起作用,是因为您多次遍历list_b (因为开始循环不止一次,并且每次循环时都会list_b “绑定到list_b ”创建一个新的迭代器)。 您会注意到重新分配并没有更改list_b第一个循环(因为迭代器继续使用旧的list ),但是在第二个循环(具有list_a的第二个值)上,它创建了一个新的迭代器,该迭代器基于list_b的新绑定值。

无论如何,这都是毫无意义的肚脐注视。 您所做的任何事都不是安全或理智的,并且“修复”(切片分配)很可能在其他Python解释器(甚至可能在将来的解释器版本)中中断。

Python作用域规则

Python具有广泛的范围。 与某些为每个if语句,for语句等创建新作用域的语言相反,Python仅具有三层作用域:局部作用域,其闭合和全局作用域。

# global scope
a = 1

def f():
    # closure of g
    b = 2

    def g():
        # local scope of g
        c = 3
        print(
            a, # get a from the global scope
            b, # get b from the closure
            c  # get c from the local scope
        )

    g()

f() # prints: 1 2 3

其他语言范围规则

例如,请考虑以下JavaScript代码段。

// JavaScript

let x = [1, 2]

for (i = 0; i < 3; i++) {
    let x = [3, 4] // The use of let declares are new variable specific to this scope
    console.log(x) // [3, 4]
}

console.log(x) // [1, 2]

我们在这里注意到的是,for循环具有自己的范围。 许多知名语言就是这种情况。 虽然,在Python中不会发生相同的情况。

# Python3

x = [1, 2]
for i in x:
    x = [3, 4]
    print(x) # [3, 4]

print(x) # [3, 4]

请注意,循环内x的分配是如何从循环中泄漏出来的。

这如何适用

让我们回顾一下您的示例。 下面的所有代码都在同一范围内发生。

list_a = [1 ,2, 3]
list_b = [11, 12, 13]

for i in list_a:
    for j in list_b:
        list_b = [100, 200] # This makes the name 'list_b' point to the value [100, 200]
        print(i, j)

    # Here the name 'list_b' is not set back to [11, 12, 13] since we are in the same scope

由于所有事情都在同一范围内发生,因此在list_a的第二次迭代中,名称list_b现在指向一个新值: [100, 200]

我们该如何解决?

由于作用域在Python中的工作原理,在迭代过程中更改可迭代对象不仅是不好的做法,而且重用它的名称也是不好的做法。 因此,您想对在for循环内定义的变量使用新名称,因为这些变量会从循环中泄漏出来。

list_a = [1 ,2, 3]
list_b = [11, 12, 13]

current_iterable = list_b
for i in list_a:
    for j in current_iterable:
        print(i, j)
    current_iterable = [100, 200]

print(list_b) # [11, 12, 3]
print(current_iterable) = [100, 200]

如您所见, current_iterable名称仍然存在于循环外部,但是至少我们没有使用仅由循环使用的值覆盖已经存在的名称。

这当然是一个经验法则,有时我们可能想要增量更新一个可迭代对象。

def breadth_first_search(initial_node):
    nodes = [initial_node]

    for node in nodes:
        node.seen = True
        queue.extend(n for n in node.neighbors if not n.seen)

    return nodes

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM