简体   繁体   English

通过切片和for循环修改Python列表?

[英]Modifying Python lists via slices and for-loops?

I was trying to modify the values in lists via slices and for-loops, and ran into some pretty interesting behavior. 我试图通过切片和for循环修改列表中的值,并遇到一些非常有趣的行为。 I would appreciate if someone could explain what's happening internally here. 如果有人可以解释这里内部发生​​的事情,我将不胜感激。

>>> x = [1,2,3,4,5]
>>> x[:2] = [6,7] #slices can be modified
>>> x
[6, 7, 3, 4, 5]
>>> x[:2][0] = 8 #indices of slices cannot be modified
>>> x
[6, 7, 3, 4, 5]
>>> x[:2][:1] = [8] #slices of slices cannot be modified
>>> x
[6, 7, 3, 4, 5]
>>> for z in x: #this version of a for-loop cannot modify lists
...    z += 1
... 
>>> x
[6, 7, 3, 4, 5]
>>> for i in range(len(x)): #this version of a for-loop can modify lists
...    x[i] += 1
... 
>>> x
[7, 8, 4, 5, 6]
>>> y = x[:2] #if I assign a slice to a var, it can be modified...
>>> y[0] = 1
>>> y
[1, 8]
>>> x #...but it has no impact on the original list
[7, 8, 4, 5, 6]

Let's break down your comments 1 by 1: 让我们按1分解您的评论1:

1.) x[:2] = [6, 7] slices can be modified: 1.) x[:2] = [6, 7]切片可以修改:

See these answers here . 在这里查看这些答案 It's calling the __setitem__ method from the list object and assigning the slice to it. 它从list对象中调用__setitem__方法并将slice分配给它。 Each time you reference x[:2] a new slice object is created (you can simple do id(x[:2]) and it's apparent, not once will it be the same id). 每次引用x[:2]都会创建一个新的slice对象(您可以简单地执行id(x[:2]) ,这很明显,不会一次出现相同的id)。

2.) indices of slices cannot be modified: 2.)切片的索引无法修改:

That's not true. 这不是真的。 It couldn't be modified because you're performing the assignment on the slice instance, not the list , so it doesn't trigger the __setitem__ to be performed on the list . 无法修改它,因为您是在slice实例而不是list上执行分配,因此不会触发__setitem__list上执行。 Also, int are immutable so it cannot be changed either way. 另外, int是不可变的,因此不能以任何方式更改。

3.) slices of slices cannot be modified: 3.)切片的切片无法修改:

See above. 往上看。 Same reason - you are assigning to an instance of the slice and not modifying the list directly. 相同的原因-您要分配给切片的实例,而不是直接修改list

4.) this version of a for-loop cannot modify lists: 4.)此版本的for循环无法修改列表:

z being referenced here is the actual objects in the elements of x . 此处引用的zx元素中的实际对象。 If you ran the for loop with id(z) you'll note that they're identical to id(6), id(7), id(3), id(4), id(5) . 如果使用id(z)运行for循环,您会注意到它们与id(6), id(7), id(3), id(4), id(5) Even though list contains all 5 identical references, when you do z = ... you are only assigning the new value to the object z , not the object that is stored in list . 即使list包含所有5个相同的引用,但是当执行z = ...您只是将新值分配给对象z ,而不是分配给list存储的对象。 If you want to modify the list , you'll need to assign it by index, for the same reason you can't expect 1 = 6 will turn x into [6, 2, 3, 4, 5] . 如果要修改list ,则需要按索引进行分配,出于同样的原因,您不能期望1 = 6x变成[6, 2, 3, 4, 5]

5.) this version of a for-loop can modify lists: 5.)此版本的for循环可以修改列表:

See my answer above. 请参阅上面的答案。 Now you are directly performing item assignment on the list instead of its representation. 现在,您将直接在list上执行项目分配,而不是其表示。

6.) if I assign a slice to a var, it can be modified: 6.)如果我将切片分配给var,则可以对其进行修改:

If you've been following so far, you'll realize now you are assigning the instance of x[:2] to the object y , which is now a list . 如果到目前为止您一直在关注,您将意识到现在正在将x[:2]的实例分配给对象y ,该对象现在是一个list The story follows - you perform an item assignment by index on y , of course it will be updated. 故事如下-您通过y上的索引执行项目分配,当然它将被更新。

7.) ...but it has no impact on the original list: 7.)...但对原始清单没有影响:

Of course. 当然。 x and y are two different objects. xy是两个不同的对象。 id(x) != id(y) , therefore any operation performed on x will not affect y whatsoever. id(x) != id(y) ,因此对x执行的任何操作均不会影响y if you however assigned y = x and then made a change to y , then yes, x will be affected as well. 但是,如果您将y = x分配给y = x ,然后将其更改为y ,那么可以, x也将受到影响。

To expand a bit on for z in x: , say you have a class foo() and assigned two instances of such to the list f : 为了扩展for z in x: ,假设您有一个class foo()并将此类的两个实例分配给列表f

f1 = foo()
f2 = foo()
f = [f1, f2]
f
# [<__main__.foo at 0x15f4b898>, <__main__.foo at 0x15f4d3c8>]

Note that the reference in question is the actual foo instance, not the object f1 and f2 . 请注意,所涉及的引用是实际的foo实例,而不是对象f1f2 So even if I did the following: 因此,即使我执行以下操作:

f1 = 'hello'
f
#  [<__main__.foo at 0x15f4b898>, <__main__.foo at 0x15f4d3c8>]

f still remains unchanged since the foo instances remains the same even though object f1 now is assigned to a different value. 即使现在将对象f1分配给其他值,由于f​​oo实例仍然相同,因此f仍保持不变。 For the same reason, whenever you make changes to z in for z in x: , you are only affecting the object z , but nothing in the list is changed until you update x by index. 出于同样的原因,只要for z in x:for z in x: z in进行更改,就只会影响对象z ,但列表中的任何内容都不会更改,直到按索引更新x为止。

If however the object have attribute or is mutable, you can directly update the referenced object in the loop: 但是,如果对象具有属性或是可变的,则可以在循环中直接更新引用的对象:

x = ['foo']
y = ['foo']
lst = [x,y]
lst
# [['foo'], ['foo']]
for z in lst:
    z.append('bar')

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

That is because you are directly updating the object in reference instead of assigning to object z . 那是因为您正在直接更新引用中的对象,而不是分配给对象z If you however assigned x or y to a new object, lst will not be affected. 但是,如果将xy分配给新对象,则lst不会受到影响。

There is nothing odd happening here. 这里没有奇怪的事情发生。 Any slice that you obtain from a list is a new object containing copies of your original list. 您从列表中获得的任何切片都是一个包含原始列表副本的新对象。 The same is true for tuples. 元组也是如此。

When you iterate through your list, you get the object which the iteration yields. 遍历列表时,将获得迭代产生的对象。 Since int s are immutable in Python you can't change the state of int objects. 由于int在Python中是不可变的,因此您无法更改int对象的状态。 Each time you add two int sa new int object is created. 每次添加两个int都会创建一个新的int对象。 So your "version of a for-loop [which] cannot modify lists" is not really trying to modify anything because it will not assign the result of the addition back to the list. 因此,您的“ for循环[该版本无法修改列表]版本”实际上并没有尝试修改任何内容,因为它不会将添加结果分配回列表。

Maybe you can guess now why your second approach is different. 也许您现在可以猜测为什么第二种方法不同。 It uses a special slicing syntax which is not really creating a slice of your list and allows you to assign to the list ( documentation ). 它使用一种特殊的切片语法,该语法实际上并没有真正创建您的列表的一部分,而是允许您分配给列表( 文档 )。 The newly created object created by the addition operation is stored in the list through this method. 通过加法操作创建的新创建的对象通过此方法存储在列表中。

For understanding your last (and your first) examples, it is important to know that slicing creates (at least for lists and tuples, technically you could override this in your own classes) a partial copy of your list. 为了理解您的最后(也是第一个)示例,重要的是要知道切片会创建列表的部分副本(至少对于列表和元组,从技术上讲,您可以在自己的类中覆盖它)。 Any change to this new object will, as you already found out, not change anything in your original list. 正如您已经发现的那样,对这个新对象的任何更改都不会更改原始列表中的任何内容。

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

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