Làm thế nào để sửa đổi danh sách các mục trong vòng lặp for?


178

Bây giờ tôi biết rằng không an toàn để sửa đổi danh sách trong một vòng lặp lặp. Tuy nhiên, giả sử tôi có một danh sách các chuỗi và tôi muốn tự tước chuỗi. Có thay thế các giá trị đột biến được tính là sửa đổi?


20
Một chuỗi không phải là một giá trị có thể thay đổi.
user470379

4
@ user470379: Việc các thành phần của danh sách có thể thay đổi được hay không không liên quan đến việc nó có an toàn hay không để sửa đổi danh sách mà chúng có trong khi lặp qua nó.
martineau

Câu trả lời:


144

Đó được coi là hình thức nghèo nàn. Thay vào đó, hãy sử dụng tính năng hiểu danh sách, với phép gán lát nếu bạn cần giữ lại các tham chiếu hiện có vào danh sách.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)

10
Việc gán lát là thông minh và tránh sửa đổi bản gốc trong vòng lặp, nhưng yêu cầu tạo ra một danh sách tạm thời độ dài của bản gốc.
martineau

11
tại sao chúng ta gán b = a?
Vigrond

9
@Vigrond: Vì vậy, khi print bcâu lệnh được thực thi, bạn có thể biết nếu ađã được sửa đổi tại chỗ chứ không phải thay thế. Một khả năng khác có thể là print b is ađể xem liệu cả hai có còn đề cập đến cùng một đối tượng hay không.
martineau

12
tại sao a [:] = và không chỉ a =?
kdub

10
@kdub: "... với phép gán lát nếu bạn cần giữ lại các tham chiếu hiện có vào danh sách."
Ignacio Vazquez-Abrams

162

Vì vòng lặp bên dưới chỉ sửa đổi các yếu tố đã thấy, nên nó sẽ được coi là chấp nhận được:

a = ['a',' b', 'c ', ' d ']

for i, s in enumerate(a):
    a[i] = s.strip()

print(a) # -> ['a', 'b', 'c', 'd']

Khác với:

a[:] = [s.strip() for s in a]

trong đó nó không yêu cầu tạo ra một danh sách tạm thời và một sự phân công của nó để thay thế cho bản gốc, mặc dù nó đòi hỏi nhiều hoạt động lập chỉ mục hơn.

Thận trọng: Mặc dù bạn có thể sửa đổi các mục theo cách này, bạn không thể thay đổi số lượng mục trong listmà không có nguy cơ gặp phải sự cố.

Đây là một ví dụ về những gì tôi muốn nói là xóa một mục nhập làm rối loạn việc lập chỉ mục từ thời điểm đó:

b = ['a', ' b', 'c ', ' d ']

for i, s in enumerate(b):
    if s.strip() != b[i]:  # leading or trailing whitespace?
        del b[i]

print(b)  # -> ['a', 'c ']  # WRONG!

(Kết quả là sai vì nó không xóa tất cả các mục cần có.)

Cập nhật

Vì đây là một câu trả lời khá phổ biến, nên đây là cách xóa các mục "tại chỗ" một cách hiệu quả (mặc dù đó không phải là câu hỏi chính xác):

b = ['a',' b', 'c ', ' d ']

b[:] = [entry for entry in b if entry.strip() == entry]

print(b)  # -> ['a']  # CORRECT

3
Tại sao Python chỉ tạo một bản sao của phần tử riêng lẻ trong cú pháp for i in a? Điều này rất phản trực giác, dường như khác với các ngôn ngữ khác và đã dẫn đến lỗi trong mã của tôi mà tôi phải gỡ lỗi trong một thời gian dài. Python Tutorial thậm chí không đề cập đến nó. Mặc dù phải có một số lý do cho nó?
xji

1
@JIXiang: Nó không tạo ra một bản sao. Nó chỉ gán tên biến vòng lặp cho các phần tử hoặc giá trị liên tiếp của thứ được lặp lại.
martineau

1
Eww, tại sao sử dụng hai tên ( a[i]s) cho cùng một đối tượng trong cùng một dòng khi bạn không phải? Tôi muốn làm nhiều hơn a[i] = a[i].strip().
Navin

3
@Navin: Bởi vì a[i] = s.strip()chỉ có một hoạt động lập chỉ mục.
martineau

1
@martineau enumerate(b)thực hiện một thao tác lập chỉ mục trên mỗi lần lặp và bạn đang thực hiện một thao tác khác với a[i] =. AFAIK không thể thực hiện vòng lặp này trong Python chỉ với 1 thao tác lập chỉ mục cho mỗi lần lặp lặp :(
Navin

18

Một biến thể cho vòng lặp nữa, có vẻ sạch hơn đối với tôi với biến thể liệt kê ():

for idx in range(len(list)):
    list[idx]=... # set a new value
    # some other code which doesn't let you use a list comprehension

19
Nhiều người xem xét sử dụng một cái gì đó như range(len(list))trong Python một mùi mã.
martineau

2
@Reishin: Vì enumeratelà một trình tạo nên nó không tạo ra một list.of tuples, nó tạo ra chúng cùng một lúc khi nó lặp qua danh sách. Cách duy nhất để biết cái nào chậm hơn sẽ là timeit.
martineau

3
@martineau có thể không đẹp lắm, nhưng theo timeit enumeratechậm hơn
Reishin

2
@Reishin: Mã điểm chuẩn của bạn không hoàn toàn hợp lệ vì nó không tính đến nhu cầu truy xuất giá trị trong danh sách tại chỉ mục đã cho - điều này cũng không được hiển thị trong câu trả lời này.
martineau

4
@Reishin: So sánh của bạn không chính xác vì lý do đó. Đó là đo các vòng lặp trên đầu trong sự cô lập. Để kết luận thời gian cần phải đo toàn bộ vòng lặp để thực hiện vì có thể giảm thiểu bất kỳ chênh lệch chi phí nào bằng các lợi ích được cung cấp cho mã bên trong vòng lặp theo một cách nhất định - nếu không bạn không so sánh táo với táo.
martineau

11

Sửa đổi từng thành phần trong khi lặp lại một danh sách là tốt, miễn là bạn không thay đổi thêm / loại bỏ các yếu tố thành danh sách.

Bạn có thể sử dụng hiểu danh sách:

l = ['a', ' list', 'of ', ' string ']
l = [item.strip() for item in l]

hoặc chỉ thực hiện C-stylevòng lặp for:

for index, item in enumerate(l):
    l[index] = item.strip()

4

Không, bạn sẽ không thay đổi "nội dung" của danh sách, nếu bạn có thể thay đổi chuỗi theo cách đó. Nhưng trong Python chúng không thể thay đổi. Bất kỳ hoạt động chuỗi trả về một chuỗi mới.

Nếu bạn có một danh sách các đối tượng bạn biết là có thể thay đổi, bạn có thể làm điều này miễn là bạn không thay đổi nội dung thực tế của danh sách.

Vì vậy, bạn sẽ cần phải làm một bản đồ của một số loại. Nếu bạn sử dụng biểu thức trình tạo, [thao tác] sẽ được thực hiện khi bạn lặp lại và bạn sẽ tiết kiệm bộ nhớ.


4

Bạn có thể làm một cái gì đó như thế này:

a = [1,2,3,4,5]
b = [i**2 for i in a]

Nó được gọi là một sự hiểu biết danh sách, để giúp bạn dễ dàng lặp lại bên trong một danh sách.


3

Câu trả lời được đưa ra bởi Jemshit Iskenderov và Ignacio Vazquez-Abrams là thực sự tốt. Nó có thể được minh họa thêm với ví dụ này: hãy tưởng tượng rằng

a) Một danh sách có hai vectơ được đưa cho bạn;

b) bạn muốn duyệt qua danh sách và đảo ngược thứ tự của từng mảng

Hãy nói rằng bạn có

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
    i = i[::-1]   # this command does not reverse the string

print([v,b])

Bạn sẽ nhận được

[array([1, 2, 3, 4]), array([3, 4, 6])]

Mặt khác, nếu bạn làm

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
   i[:] = i[::-1]   # this command reverses the string

print([v,b])

Kết quả là

[array([4, 3, 2, 1]), array([6, 4, 3])]

1

Vẫn chưa rõ câu hỏi của bạn về tiêu chí quyết định loại bỏ chuỗi nào, nhưng nếu bạn có hoặc có thể lập danh sách các chuỗi mà bạn muốn xóa, bạn có thể thực hiện như sau:

my_strings = ['a','b','c','d','e']
undesirable_strings = ['b','d']
for undesirable_string in undesirable_strings:
    for i in range(my_strings.count(undesirable_string)):
        my_strings.remove(undesirable_string)

thay đổi my_strings thành ['a', 'c', 'e']


0

Nói tóm lại, để thực hiện sửa đổi trong danh sách trong khi lặp lại cùng một danh sách.

list[:] = ["Modify the list" for each_element in list "Condition Check"]

thí dụ:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.