Python 列表与遍历访问(在内置列表中查找/替换)
我最开始以为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 个回答
你可以通过获取索引和项目来替换列表中的某个元素。
>>> 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']
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
。
回答这个问题让我受益匪浅,因为评论让我对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]
需要注意的是,你仍然没有改变列表本身,只是修改了列表中的对象。
你可以看看命名和绑定这部分内容,可能会对你有帮助。