Python 列表与遍历访问(在内置列表中查找/替换)

30 投票
3 回答
115695 浏览
提问于 2025-04-17 02:19

我最开始以为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

3 个回答

6

你可以通过获取索引和项目来替换列表中的某个元素。

>>> 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']
16

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 = new_list

49

回答这个问题让我受益匪浅,因为评论让我对Python变量有了更深的理解。

正如评论中提到的,当你用类似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]

需要注意的是,你仍然没有改变列表本身,只是修改了列表中的对象。

你可以看看命名和绑定这部分内容,可能会对你有帮助。

撰写回答