Lặp lại hai lần trong danh sách hiểu


226

Trong Python bạn có thể có nhiều trình vòng lặp trong một danh sách hiểu, như

[(x,y) for x in a for y in b]

cho một số trình tự phù hợp a và b. Tôi biết về ngữ nghĩa vòng lặp lồng nhau của việc hiểu danh sách của Python.

Câu hỏi của tôi là: một trình lặp có thể hiểu được cái kia không? Nói cách khác: Tôi có thể có một cái gì đó như thế này:

[x for x in a for a in b]

trong đó giá trị hiện tại của vòng lặp bên ngoài là vòng lặp của bên trong?

Ví dụ, nếu tôi có một danh sách lồng nhau:

a=[[1,2],[3,4]]

biểu thức hiểu danh sách sẽ là gì để đạt được kết quả này:

[1,2,3,4]

?? (Xin vui lòng chỉ liệt kê câu trả lời hiểu, vì đây là những gì tôi muốn tìm hiểu).

Câu trả lời:


178

Để trả lời câu hỏi của bạn với đề xuất của riêng bạn:

>>> [x for b in a for x in b] # Works fine

Trong khi bạn yêu cầu câu trả lời hiểu danh sách, tôi cũng chỉ ra itertools.chain ():

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6

11
[x for b in a for x in b]Điều này đã luôn luôn có lỗi về con trăn. Cú pháp này rất ngược. Dạng tổng quát x for x in yluôn có biến trực tiếp sau for, feed cho biểu thức ở bên trái của for. Ngay khi bạn thực hiện một sự hiểu biết kép, biến lặp gần đây nhất của bạn đột nhiên rất "xa". Thật là khó xử và hoàn toàn không đọc một cách tự nhiên
Cruncher

170

Tôi hy vọng điều này sẽ giúp người khác vì a,b,x,ytôi không có nhiều ý nghĩa với tôi! Giả sử bạn có một văn bản đầy câu và bạn muốn một mảng các từ.

# Without list comprehension
list_of_words = []
for sentence in text:
    for word in sentence:
       list_of_words.append(word)
return list_of_words

Tôi thích nghĩ về việc hiểu danh sách như kéo dài mã theo chiều ngang.

Hãy thử chia nó thành:

# List Comprehension 
[word for sentence in text for word in sentence]

Thí dụ:

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> [word for sentence in text for word in sentence]
['Hi', 'Steve!', "What's", 'up?']

Điều này cũng hoạt động cho máy phát điện

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> gen = (word for sentence in text for word in sentence)
>>> for word in gen: print(word)
Hi
Steve!
What's
up?

8
"Chỉ có hai vấn đề khó khăn trong Khoa học Máy tính: vô hiệu hóa bộ đệm và đặt tên mọi thứ." - Phil Karlton
cezar

Đây là một câu trả lời tuyệt vời vì nó làm cho toàn bộ vấn đề bớt trừu tượng! Cảm ơn bạn!
A. Blesius

Tôi đã tự hỏi, bạn có thể làm tương tự với ba cấp độ trừu tượng trong một danh sách hiểu? Giống như chương trong văn bản, câu trong chương và từ trong câu?
Thuyền trưởng Fogetti

123

Gee, tôi đoán tôi đã tìm thấy anwser: Tôi đã không quan tâm đầy đủ về vòng lặp nào là bên trong và bên ngoài. Việc hiểu danh sách nên như sau:

[x for b in a for x in b]

để có được kết quả mong muốn và có, một giá trị hiện tại có thể là vòng lặp cho vòng lặp tiếp theo.


67
Cú pháp hiểu danh sách không phải là một trong những điểm sáng của Python.
Glenn Maynard

2
@Glenn Vâng, nó dễ dàng bị gây nhiễu cho nhiều hơn các biểu thức đơn giản.
ThomasH

1
Ew. Tôi không chắc đây là cách sử dụng "thông thường" để hiểu danh sách, nhưng thật không may là chuỗi rất khó chịu trong Python.
Matt Joiner

14
Nó trông rất sạch sẽ nếu bạn đặt dòng mới trước mỗi 'cho'.
Nick Garvey

16
Wow, điều này hoàn toàn ngược lại với những gì có ý nghĩa trong đầu tôi.
obskyr

51

Thứ tự lặp có vẻ phản trực giác.

Lấy ví dụ: [str(x) for i in range(3) for x in foo(i)]

Hãy phân hủy nó:

def foo(i):
    return i, i + 0.5

[str(x)
    for i in range(3)
        for x in foo(i)
]

# is same as
for i in range(3):
    for x in foo(i):
        yield str(x)

4
Thật là một cái mở mắt !!
nehem

Sự hiểu biết của tôi là lý do cho điều này là "lần lặp đầu tiên được liệt kê là lần lặp lại trên cùng sẽ được gõ nếu sự hiểu được viết là lồng nhau cho các vòng lặp". Lý do điều này là phản trực giác là vòng lặp OUTER (trên cùng nếu được viết dưới dạng các vòng lặp lồng nhau) xuất hiện ở bên trong danh sách / dict (đối tượng hiểu) được đặt trong ngoặc đơn. Ngược lại, vòng INNER (trong cùng khi được viết là vòng lặp lồng nhau) chính xác là vòng lặp ngoài cùng bên phải trong một sự hiểu, và theo cách đó xuất hiện ở BÊN NGOÀI của sự hiểu.
Zach Siegel

Trừu tượng bằng văn bản chúng tôi có [(output in loop 2) (loop 1) (loop 2)]với (loop 1) = for i in range(3)(loop 2) = for x in foo(i):(output in loop 2) = str(x).
Qaswed

20

ThomasH đã thêm một câu trả lời hay, nhưng tôi muốn chỉ ra điều gì xảy ra:

>>> a = [[1, 2], [3, 4]]
>>> [x for x in b for b in a]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

>>> [x for b in a for x in b]
[1, 2, 3, 4]
>>> [x for x in b for b in a]
[3, 3, 4, 4]

Tôi đoán Python phân tích cú pháp hiểu danh sách từ trái sang phải. Điều này có nghĩa, forvòng lặp đầu tiên xảy ra sẽ được thực hiện đầu tiên.

"Vấn đề" thứ hai của vấn đề này là b"bị rò rỉ" ra khỏi danh sách hiểu. Sau khi hiểu danh sách thành công đầu tiên b == [3, 4].


3
Điểm thú vị. Tôi đã rất ngạc nhiên về điều này:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch

2
Rò rỉ này đã được sửa trong Python 3: stackoverflow.com/questions/4198906/ từ
Denilson Sá Maia

10

Nếu bạn muốn giữ mảng đa chiều, người ta nên lồng các dấu ngoặc mảng. xem ví dụ dưới đây, nơi một được thêm vào mọi yếu tố.

>>> a = [[1, 2], [3, 4]]

>>> [[col +1 for col in row] for row in a]
[[2, 3], [4, 5]]

>>> [col +1 for row in a for col in row]
[2, 3, 4, 5]

8

Kỹ thuật bộ nhớ này giúp tôi rất nhiều:

[ <RETURNED_VALUE> <OUTER_LOOP1> <INNER_LOOP2> <INNER_LOOP3> ... <OPTIONAL_IF> ]

Và bây giờ bạn có thể nghĩ về R eturn + O uter-loop là R ight O rder duy nhất

Biết ở trên, thứ tự trong danh sách toàn diện thậm chí cho 3 vòng có vẻ dễ dàng:


c=[111, 222, 333]
b=[11, 22, 33]
a=[1, 2, 3]

print(
  [
    (i, j, k)                            # <RETURNED_VALUE> 
    for i in a for j in b for k in c     # in order: loop1, loop2, loop3
    if i < 2 and j < 20 and k < 200      # <OPTIONAL_IF>
  ]
)
[(1, 11, 111)]

bởi vì ở trên chỉ là một:

for i in a:                         # outer loop1 GOES SECOND
  for j in b:                       # inner loop2 GOES THIRD
    for k in c:                     # inner loop3 GOES FOURTH
      if i < 2 and j < 20 and k < 200:
        print((i, j, k))            # returned value GOES FIRST

để lặp lại một danh sách / cấu trúc lồng nhau, technic là như nhau: đối avới câu hỏi:

a = [[1,2],[3,4]]
[i2    for i1 in a      for i2 in i1]
which return [1, 2, 3, 4]

cho một cấp độ lồng nhau

a = [[[1, 2], [3, 4]], [[5, 6], [7, 8, 9]], [[10]]]
[i3    for i1 in a      for i2 in i1     for i3 in i2]
which return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

và như thế


Cảm ơn, nhưng những gì bạn mô tả thực sự là trường hợp đơn giản trong đó các trình vòng lặp liên quan là độc lập. Trong thực tế, trong ví dụ của bạn, bạn có thể sử dụng các trình vòng lặp theo bất kỳ thứ tự nào và sẽ nhận được cùng một danh sách kết quả (thứ tự modulo). Trường hợp mà tôi quan tâm hơn là với các danh sách lồng nhau trong đó một trình vòng lặp trở thành lần lặp tiếp theo.
ThomasH

@ThomasH: thứ tự của vòng lặp được xác định in đậm là chính xác cho nhu cầu của bạn. Ở phía dưới đã thêm một ví dụ để bao gồm dữ liệu của bạn và một ví dụ nữa với mức lồng nhau thêm.
Sławomir Lenart

5

Tôi cảm thấy điều này dễ hiểu hơn

[row[i] for row in a for i in range(len(a))]

result: [1, 2, 3, 4]

3

Ngoài ra, bạn có thể sử dụng cùng một biến cho thành viên của danh sách đầu vào hiện đang truy cập cho thành phần bên trong thành viên này. Tuy nhiên, điều này thậm chí có thể làm cho nó nhiều hơn (danh sách) không thể hiểu được.

input = [[1, 2], [3, 4]]
[x for x in input for x in x]

Đầu tiên for x in inputđược đánh giá, dẫn đến một danh sách thành viên của đầu vào, sau đó, Python đi qua phần thứ hai for x in xtrong đó giá trị x được ghi đè bởi phần tử hiện tại mà nó đang truy cập, sau đó đầu tiên xxác định những gì chúng ta muốn trả về.


1

Hàm flatten_nlevel này gọi đệ quy list1 lồng nhau để chuyển sang một cấp. Thử thứ này đi

def flatten_nlevel(list1, flat_list):
    for sublist in list1:
        if isinstance(sublist, type(list)):        
            flatten_nlevel(sublist, flat_list)
        else:
            flat_list.append(sublist)

list1 = [1,[1,[2,3,[4,6]],4],5]

items = []
flatten_nlevel(list1,items)
print(items)

đầu ra:

[1, 1, 2, 3, 4, 6, 4, 5]

1
Ok, câu hỏi đặc biệt là về khả năng hiểu danh sách, và làm phẳng danh sách chỉ là một ví dụ. Nhưng tôi giả sử, flattener danh sách tổng quát của bạn sẽ cần phải gọi chính nó đệ quy. Vì vậy, nó có thể giống như flatten_nlevel(sublist, flat_list), phải không?!
ThomasH
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.