【问题标题】:In Python, is there a way to update a container using the foreach-style construct?在 Python 中,有没有办法使用 foreach 样式的构造来更新容器?
【发布时间】:2019-03-29 05:30:48
【问题描述】:

我假设Python 中的 foreach 样式结构将允许我像在C# 中那样更新我的列表。没有。

经过一番调查,我发现 foreach 样式构造中Python 中使用的变量不是引用,而是一个单独的标量变量,因此我无法使用它来更新我的容器。有没有办法使用 foreach 样式更新容器?

这是一些演示我的问题的代码:

inputString = "   Type X Widgets  ,  25, 14.20 ,  Type Y Widgets , 4  , 1.12 "
inputList = inputString.split(',')
print(inputList) # Now I need to get rid of whitespace on the ends of each element

# The foreach-style does NOT update inputList
for element in inputList:
    element = element.strip()
    print(element, end=",") # element contains the stripped string as I wanted
print()
print(inputList) # the whitespace on the ends of the elements is still there

# The for-style with subscripts DOES update inputList
for i in range(len(inputList)):
    inputList[i] = inputList[i].strip()
    print(inputList[i], end=",") # inputList[i] contains the stripped string as I wanted
print()
print(inputList) # it finally contains the stripped strings with no whitespace on the ends

这是上面的输出:

['   Type X Widgets  ', '  25', ' 14.20 ', '  Type Y Widgets ', ' 4  ', ' 1.12 ']
Type X Widgets,25,14.20,Type Y Widgets,4,1.12,
['   Type X Widgets  ', '  25', ' 14.20 ', '  Type Y Widgets ', ' 4  ', ' 1.12 ']
Type X Widgets,25,14.20,Type Y Widgets,4,1.12,
['Type X Widgets', '25', '14.20', 'Type Y Widgets', '4', '1.12']

第一个 for 循环不会更新容器。第二个可以。在这个简单的情况下,我必须使用下标并不重要,但我真的希望能够在下标根本不起作用时使用 foreach 样式来更新更复杂类型的容器。

我可以在C# 中做到这一点,这是一个非常强大的工具。这在Python 中是否可能通过除了我在第一个循环中尝试的之外做一些事情? (如果是这样,我想它会涉及使用指针。Python 甚至有指针吗?)

【问题讨论】:

  • “我可以在 Java 和 C# 中做到这一点,而且它是一个非常强大的工具” - 我不确定 C#,但你绝对不能在 Java 中做到这一点。
  • 显然你可以在 C# 中,by declaring the loop variable with ref.
  • Python 没有指针。如果你给出一个更复杂类型的容器的例子会很有帮助,但无论如何,你将总是需要使用一个mutator方法来改变一个对象。 python中的赋值永远不会发生变化。因此,如果您提供一个您想到的容器示例,也许会更有帮助,我们可以向您展示 Python 的做法?
  • “我发现 Python 中在 foreach 样式结构中使用的变量不是引用,而是一个单独的标量变量”我不确定您所说的“单独的标量变量”是什么意思与引用相反,但 Python 变量的行为类似于引用(即它们不会在赋值时创建副本)
  • 对于从 C 等语言开始使用 Python 的人来说,这绝对是必不可少的读物,顺便说一句:nedbatchelder.com/text/names.html 它是由 StackOverflow 的传奇人物 Ned Batchelder 编写的。但是,如果来自 C,您实际上可以将 Python 变量视为指向 PyObject 结构的指针,除非您不能直接取消引用它们,而改变它们的唯一方法是使用这些 PyObject 上的方法。实际上,您可以将some_object[i] = x 视为some_object.__setitem__(i, x) 的语法糖

标签: python list for-loop foreach containers


【解决方案1】:

在这种特定情况下,您不能。那是因为您将对象重新分配到了一个新的引用。

在这样的“foreach”类型的迭代中:

for element in inputList:

您正在遍历列表中元素本身的对象。但在这种情况下,它是一个str 对象,它是不可变的。即当您尝试分配此行时:

element = element.strip()

您正在使用 new 对象从原始内容的剥离内容中重新分配 element。由于它是一个新对象,它与inputList 本身没有任何关系。

然而,在第二个示例中,您现在正在基于inputList 遍历一个索引列表

for i in range(len(inputList)):
# range(len(inputList)) -> range(0, 6)

当您遍历列表时,请注意您正在重新分配 inputList 的特定索引:

inputList[i] = inputList[i].strip()

这会将 new 对象分配回inputList[i]。它不再是您曾经在列表中拥有的 str 对象。

话虽如此,在其他用例中,您正在寻找的“foreach”工作得很好,只要对象是可变的。请注意以下示例:

lst = [[] for _ in range(5)]
lst
# [[], [], [], [], []]
for i in lst:
    i.append('foo')

lst
# [['foo'], ['foo'], ['foo'], ['foo'], ['foo']]

注意这里的区别:i 不是重新分配,而是直接由append() 方法更改。为了进一步证明 i 是您期望的直接对象引用,如果我在迭代完成后这样做

i.append('bar')
lst
# [['foo'], ['foo'], ['foo'], ['foo'], ['foo', 'bar']]

id(i)
# 61353816
id(lst[-1])
# 61353816

看看lstlast 元素现在是如何附加的。那是因为i 仍然保留引用。 id() 还显示了您所要求的确切证据。

如果我要这样写迭代:

for i in lst:
    i = ['foo']

lst
# [[], [], [], [], []]
id(i)
# 61354112 <-- different obj id
id(lst[-1])
# 61353816

由于与您的示例没有相同的原因,它不再起作用。因为i 现在已被重新分配给一个新对象,而不是迭代中的直接对象引用。注意对象 id 的不同。

【讨论】:

    【解决方案2】:

    嗯。在 for 循环中,在每次迭代期间,您的变量(在您的情况下为 element)将被分配给列表中下一个值(对象)的副本,而不是此值(对象)的引用。 (这并不完全正确,但你知道我想说什么)。因此,要解决您的项目,您可以执行以下操作:

    for element in inputList:
        inputList[inputList.index(element)] = element.strip()
    print(inputList)
    

    请注意,当您的列表中有两次相同的元素时,这将不起作用。

    希望对您有所帮助!

    【讨论】:

    • 这是危险的,而且效率低得离谱。
    • 这无缘无故地引入了二次时间行为。只需使用enumerate 甚至range(len(...))
    • element 不是对象的副本。它inputList元素的直接对象引用。重新分配时引用会发生变化,即element = ...。详情见我的回答。
    • 感谢您的尝试,但我试图避免使用下标。如果我必须使用它们,我的代码中的第二种方式似乎比这更好。
    【解决方案3】:

    也许这不是您想要的,但一种简单的方法是创建一个新列表,如下所示:

    inputList = [
        element.strip()
        for element in inputList]
    

    这会将新列表分配给相同的变量,替换旧的(旧的将在此之后的一段时间内被垃圾收集)。

    缺点是,这会使已用内存量增加一倍;上述语句完成后,旧列表可以被垃圾回收,但内存使用量仍然会激增。

    【讨论】:

      猜你喜欢
      • 2011-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-01
      • 1970-01-01
      相关资源
      最近更新 更多