Python 列表和 foreach 访问(在内置列表中查找/替换)

Posted

技术标签:

【中文标题】Python 列表和 foreach 访问(在内置列表中查找/替换)【英文标题】:Python List & for-each access (Find/Replace in built-in list) 【发布时间】:2011-11-17 09:31:50 【问题描述】:

我最初认为 Python 是一种纯粹的引用传递语言。

来自 C/C++ 的我不禁想到内存管理,而且很难将其从脑海中抹去。所以我试图从 Java 的角度来考虑它,并将除原语之外的所有内容都视为通过引用传递。

问题:我有一个列表,其中包含一组用户定义类的实例。

如果我使用 for-each 语法,即:

for member in my_list:
    print(member.str);

member 是否等同于对对象的实际引用?

是否相当于做:

i = 0
while i < len(my_list):
    print(my_list[i])
    i += 1

我认为不是,因为当我要进行替换时,它不起作用,也就是说,这不起作用:

for member in my_list:
    if member == some_other_obj:
        member = some_other_obj

在列表中进行简单的查找和替换。可以在for-each循环中完成吗,如果可以,如何?否则,我是否只需要使用随机访问语法(方括号),或者两者都不起作用,我需要删除条目并插入一个新条目?即:

i = 0
for member in my_list:
   if member == some_other_obj:
      my_list.remove(i)
      my_list.insert(i, member)
   i += 1

【问题讨论】:

在列表的迭代过程中不会复制值。原因和为什么objects are not copied when passed as parameters to a function类似。 【参考方案1】:

回答这个问题很好,因为 cmets 提高了我自己对 Python 变量的理解。

如 cmets 中所述,当您循环使用类似 for member in my_list 的列表时,member 变量将绑定到每个连续的列表元素。但是,在循环中重新分配该变量不会直接影响列表本身。例如,此代码不会更改列表:

my_list = [1,2,3]
for member in my_list:
    member = 42
print my_list

输出:

[1, 2, 3]

如果要更改包含不可变类型的列表,则需要执行以下操作:

my_list = [1,2,3]
for ndx, member in enumerate(my_list):
    my_list[ndx] += 42
print my_list

输出:

[43, 44, 45]

如果你的列表中包含可变对象,可以直接修改当前的member对象:

class C:
    def __init__(self, n):
        self.num = n
    def __repr__(self):
        return str(self.num)

my_list = [C(i) for i in xrange(3)]
for member in my_list:
    member.num += 42
print my_list

[42, 43, 44]

请注意,您仍然没有更改列表,只是修改了列表中的对象。

阅读Naming and Binding,您可能会受益。

【讨论】:

不幸的是,它被重新定义为列表成员的副本。我相信作为参考出现会更有用,就像你在设置它一样,它可能是以某种方式操纵结构。我测试了语法:for idx in range(0, len(my_list)): my_list[idx] = new_obj 它符合我的喜好。谢谢。 @Syndacate:不知道 C++ 如何处理这样的事情,但 Java 版本的 for each 循环(或用他们的术语来说是增强的 for 循环)以类似的方式工作,至少在效果上。跨度> @J.F. Sebastian:我可能没有完美的术语,但我相信我已经提供了一个很好的方法来思考这个问题。答案已更改和扩展。 @GreenMatt:除非您编辑答案,否则我无法撤销我的投票(这是 SO 的一项功能)。 我记得整个“名称是指对象。名称是通过名称绑定操作引入的”术语我看到python.net/~goodger/projects/pycon/2007/idiomatic/…时刚刚点击所以你不要改变change member 你只需将它绑定到另一个对象。尽管member 在任何时候都不是列表中的对象之一,但它引用了它们中的每一个。【参考方案2】:

Python 不是 Java,也不是 C/C++——你需要停止这样的想法才能真正利用 Python 的力量。

Python 没有按值传递,也没有按引用传递,而是使用按名称传递(或按对象传递)——换句话说,几乎所有东西都绑定到一个名称然后您可以使用(元组索引和列表索引这两个明显的例外)。

当您执行spam = "green" 时,您已将名称spam 绑定到字符串对象"green";如果你然后做eggs = spam 你没有复制任何东西,你还没有创建引用指针;您只需将另一个名称eggs 绑定到同一个对象(在本例中为"green")。如果您随后将 spam 绑定到其他东西 (spam = 3.14159),eggs 仍将绑定到 "green"

当一个for循环执行时,它会取你给它的名字,并在循环运行时将它依次绑定到可迭代对象中的每个对象;当您调用函数时,它会获取函数头中的名称并将它们绑定到传递的参数;重新分配名称实际上是重新绑定名称(可能需要一段时间才能吸收这一点——无论如何,它对我来说确实如此)。

对于利用列表的 for 循环,有两种基本的方法可以分配回列表:

for i, item in enumerate(some_list):
    some_list[i] = process(item)

new_list = []
for item in some_list:
    new_list.append(process(item))
some_list[:] = new_list

注意最后一个some_list 上的[:]——它导致some_list 的元素发生突变(将整个事物设置为new_list 的元素),而不是将名称some_list 重新绑定到new_list。这很重要吗?这取决于!如果你有除some_list之外的其他名字绑定到同一个列表对象,并且你想让他们看到更新,那么你需要使用切片的方法;如果您不这样做,或者如果您确实希望他们看到更新,请重新绑定 -- some_list = new_list

【讨论】:

名称绑定的工作方式与 Java 中的相同。我看不出有什么不同。 在 java 中,int 和 booleans 等原语是按值传递的,而不是原语的所有东西都是通过引用传递的。绑定到名称类似于通过引用传递,减去类型安全性和可能的​​一些其他细节。在 java 中,一个引用至少在编译时维护着关于它可以引用什么的类型数据,python 名称没有这样的限制。【参考方案3】:

您可以通过获取索引以及项目来替换其中的某些内容。

>>> foo = ['a', 'b', 'c', 'A', 'B', 'C']
>>> for index, item in enumerate(foo):
...     print(index, item)
...
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'A')
(4, 'B')
(5, 'C')
>>> for index, item in enumerate(foo):
...     if item in ('a', 'A'):
...         foo[index] = 'replaced!'
...
>>> foo
['replaced!', 'b', 'c', 'replaced!', 'B', 'C']

请注意,如果您想从列表中删除某些内容,您必须遍历列表的副本,否则您会收到错误,因为您试图更改您正在迭代的内容的大小。使用切片可以很容易地做到这一点。

错误:

>>> foo = ['a', 'b', 'c', 1, 2, 3]
>>> for item in foo:
...     if isinstance(item, int):
...         foo.remove(item)
...
>>> foo 
['a', 'b', 'c', 2]

2 仍然存在,因为我们在迭代列表时修改了列表的大小。正确的做法是:

>>> foo = ['a', 'b', 'c', 1, 2, 3]
>>> for item in foo[:]:
...     if isinstance(item, int):
...         foo.remove(item)
...
>>> foo 
['a', 'b', 'c']

【讨论】:

@neurino:pythonic 方法是:foo = [c for c in foo if condition(c)] @J.F. Sebastian:老实说,我不认为单行代码中的所有内容都比 3 行代码更 Pythonic,我想强调 Gilder 使用的是 enumerate 而 GreenMat 被卡住 (he edited his answer) 到 for x in xrange(len()) .干杯 @neurino:当然,我是一个“老派”程序员,试图自己更新我的技能;因此我倾向于首先想到for x in xrange(len(l))。因此,我希望 for x in xrange(len(l))for x in enumerate(l) 更熟悉来自 C/C++(OP 自称是)的人。是更加 Pythonic 还是专注于核心问题并在示例所必需但不是问题核心的事情上使用更熟悉的语法更好? (我认为没有一个正确的答案。) @neurino:我使用列表推导将O(N**2) 循环替换为foo[:] 中的foo.remove()(第三个示例)。我不反对enumerate() 的第一个例子。应该很明显,因为第 1 和第 3 个示例做了不同的事情,并且列表理解产生了与第 3 个示例相似的结果。 @GreenMatt:我同意 pythonic 不是强制性的,但是如果我从未听说过 javanic*C*inic 是因为 python 提供了一个编程和编写代码的思维方式略有不同,恕我直言,越早掌握越好。无论如何,在某些情况下,您只需要索引而不是其他任何东西,在这种情况下,我会选择xrange(len(l)),并且不会觉得自己不那么pythonic ... :)

以上是关于Python 列表和 foreach 访问(在内置列表中查找/替换)的主要内容,如果未能解决你的问题,请参考以下文章

python内置数据类型列表list和字典dict的性能

传递给控制器​​时,foreach 循环中内置的模型为空

Python 列表list详解(超详细)

Python 6-1.内置数据结构之list(基础篇)

Python3-内置类型-列表与元组类型

python迭代器和生成器(3元运算,列表生成式,生成器表达式,生成器函数)