2012-06-02 29 views
7

Nếu tôi tạo hai danh sách và nén chúngLàm thế nào để ngăn chặn trình lặp bị cạn kiệt trong Python (3.x)?

a=[1,2,3] 
b=[7,8,9] 
z=zip(a,b) 

Sau đó, tôi định kiểu z thành hai danh sách

l1=list(z) 
l2=list(z) 

Sau đó, các nội dung của l1 bật ra được tốt [(1,7), (2 , 8), (3,9)], nhưng nội dung của l2 chỉ là [].

Tôi đoán đây là hành vi chung của python liên quan đến vòng lặp. Nhưng với tư cách là một lập trình viên mới gia nhập từ gia đình C, điều này không có ý nghĩa với tôi. Tại sao nó hoạt động theo cách như vậy? Và có cách nào để vượt qua vấn đề này không? Tôi có nghĩa là, yeah trong ví dụ cụ thể này, tôi chỉ có thể sao chép l1 vào l2, nhưng nói chung là có một cách để 'thiết lập lại' bất cứ điều gì Python sử dụng để lặp lại 'z' sau khi tôi lặp lại nó một lần không? Không.

+0

Hành vi của * máy phát *, không phải tất cả các lần lặp. Ví dụ, danh sách có thể lặp lại và bạn có thể gọi 'list (a) 'và nhận các bản sao của' a' nhiều như bạn muốn. –

Trả lời

8

Không có cách nào để "đặt lại" trình tạo. Tuy nhiên, bạn có thể sử dụng itertools.tee để "sao chép" một trình lặp.

>>> z = zip(a, b) 
>>> zip1, zip2 = itertools.tee(z) 
>>> list(zip1) 
[(1, 7), (2, 8), (3, 9)] 
>>> list(zip2) 
[(1, 7), (2, 8), (3, 9)] 

Điều này liên quan đến giá trị bộ nhớ đệm, do đó, chỉ có ý nghĩa nếu bạn đang lặp qua cả hai lần lặp với cùng tốc độ. (Nói cách khác, không sử dụng nó theo cách tôi có ở đây!)

Cách tiếp cận khác là truyền xung quanh chức năng của máy phát và gọi nó bất cứ khi nào bạn muốn lặp lại nó.

def gen(x): 
    for i in range(x): 
     yield i ** 2 

def make_two_lists(gen): 
    return list(gen()), list(gen()) 

Nhưng bây giờ bạn phải liên kết đối số với chức năng của trình tạo khi bạn truyền. Bạn có thể sử dụng lambda cho điều đó, nhưng rất nhiều người tìm thấy lambda xấu xí. (Không phải tôi mặc dù! YMMV.)

>>> make_two_lists(lambda: gen(10)) 
([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]) 

Tôi hy vọng điều đó sẽ không xảy ra nếu bạn tạo danh sách và sao chép nó.

Ngoài ra, như một cách tổng quát hơn để giải thích hành vi này, hãy xem xét điều này. Điểm của máy phát điện là tạo ra một loạt các giá trị, trong khi duy trì một số trạng thái giữa các lần lặp. Bây giờ, vào những thời điểm, thay vì chỉ đơn giản lặp lại trên một máy phát điện, bạn có thể muốn làm một cái gì đó như thế này:

z = zip(a, b) 
while some_condition(): 
    fst = next(z, None) 
    snd = next(z, None) 
    do_some_things(fst, snd) 
    if fst is None and snd is None: 
     do_some_other_things() 

Hãy nói rằng vòng lặp này thể hoặc có thể không xả z. Bây giờ chúng ta có một máy phát điện ở trạng thái không xác định! Vì vậy, điều quan trọng là tại thời điểm này, hành vi của một máy phát điện bị hạn chế theo một cách được xác định rõ ràng. Mặc dù chúng ta không biết máy phát ở đâu trong đầu ra, chúng ta biết rằng a) tất cả các truy cập tiếp theo sẽ tạo ra các giá trị sau trong chuỗi và b) khi nó "trống", chúng tôi đã nhận tất cả các mục trong chuỗi chính xác một lần. Càng có nhiều khả năng chúng ta phải thao túng trạng thái của z, thì càng khó để giải thích về nó, vì vậy tốt nhất là chúng ta tránh các tình huống phá vỡ hai lời hứa đó.

Tất nhiên, như Joel Cornett chỉ ra bên dưới, nó có thể viết máy phát điện chấp nhận tin nhắn qua phương thức send; và nó sẽ có thể viết một máy phát điện có thể được thiết lập lại bằng cách sử dụng send. Nhưng lưu ý rằng trong trường hợp đó, tất cả những gì chúng tôi có thể làm là gửi tin nhắn. Chúng ta không thể thao tác trực tiếp trạng thái của máy phát, và vì vậy tất cả các thay đổi đối với trạng thái của máy phát được xác định rõ (bởi chính bộ tạo - giả sử nó được viết chính xác!). send thực sự là để thực hiện coroutines, vì vậy tôi sẽ không sử dụng nó cho mục đích này. Máy phát điện hàng ngày hầu như không bao giờ làm bất cứ điều gì với các giá trị được gửi đến chúng - Tôi nghĩ vì những lý do tôi đưa ra ở trên.

+0

Ngoài ra còn có tính năng 'send()'. –

+0

Tính năng này hoạt động nhưng phức tạp và quá mức, IMO. "Không sử dụng nó như tôi làm" là một gợi ý của điều đó là tốt. :-) –

+0

@LennartRegebro, tốt, tôi nghĩ rằng 'tee' tồn tại vì một lý do chính đáng, và đó là điều gần nhất trong libs chuẩn mà tôi có thể nghĩ đến chức năng mà OP đã yêu cầu. Tôi cho rằng OP đã biết rằng có thể sao chép danh sách! – senderle

1

Không, không có cách nào để "đặt lại chúng".

Máy phát điện tạo ra đầu ra một lần, từng cái một, theo yêu cầu, và sau đó được thực hiện khi đầu ra cạn kiệt.

Hãy nghĩ về chúng như đọc tệp, khi bạn đã hoàn thành, bạn sẽ phải khởi động lại nếu bạn muốn có dữ liệu khác.

Nếu bạn cần giữ đầu ra của máy phát xung quanh, sau đó xem xét lưu trữ nó, ví dụ, trong một danh sách, và sau đó tái sử dụng nó thường xuyên như bạn cần. (Hơi giống với các quyết định hướng dẫn việc sử dụng các xrange(), một máy phát điện vs range() đó tạo ra một danh sách toàn bộ các hạng mục trong bộ nhớ trong v2)

Cập nhật: chỉnh thuật ngữ, tạm não cúp ...

+0

Những gì bạn mô tả về cơ bản được thực hiện bởi 'itertools.tee()', như được mô tả trong câu trả lời của người gửi. 1 từ tôi, mặc dù, như cuộc thảo luận của bạn là có liên quan. – EOL

2

Just tạo danh sách ra khỏi trình lặp của bạn bằng cách sử dụng list() một lần và sử dụng nó sau đó.

Nó chỉ xảy ra rằng zip trả về một máy phát điện , mà là một iterator rằng bạn chỉ có thể lặp một lần.

Bạn có thể lặp lại danh sách bao nhiêu lần tùy thích.

+2

Nó không thực sự "đúc", nhưng đây thường là cách tiếp cận tốt nhất. Ngoài ra, bạn đã xác định chính xác rằng 'vòng lặp' là một danh mục siêu bao gồm cả trình tạo và trình tự, do đó, điều này sẽ nhận được phiếu bầu của tôi cho câu trả lời hay nhất. –

+0

@KarlKnechtel bạn hoàn toàn đúng, tôi đã chỉnh sửa điều đó! –

2

Nếu bạn cần hai bản sao của danh sách, mà bạn làm gì nếu bạn cần phải sửa đổi chúng, sau đó tôi đề nghị bạn thực hiện danh sách một lần, và sau đó sao chép nó:

a=[1,2,3] 
b=[7,8,9] 
l1 = list(zip(a,b)) 
l2 = l1[:] 
+0

Đúng như tôi đã đề cập, tôi * có thể * chỉ cần sao chép danh sách đầu tiên. Tôi hỏi câu hỏi này chỉ vì tôi muốn được rõ ràng trong các khái niệm Python của tôi. Dù sao cũng cảm ơn bạn! – user1265125

0

Tuy nhiên, một lời giải thích. Là một lập trình viên, bạn có thể hiểu sự khác biệt giữa các lớp so với các cá thể (tức là các đối tượng). Các zip() được cho là một được xây dựng trong chức năng (trong tài liệu chính thức). Trên thực tế, đây là một máy phát điện được xây dựng trong máy phát điện chức năng. Nó có nghĩa là nó là một lớp học. Bạn thậm chí có thể thử ở chế độ tương tác:

>>> zip 
<class 'zip'> 

Các loại là loại. Do đó cũng như sau phải rõ ràng:

>>> type(zip) 
<class 'type'> 

bạn z là thể hiện của lớp, và bạn có thể suy nghĩ về cách gọi zip() như về cách gọi constructor lớp:

>>> a = [1, 2, 3] 
>>> b = [7, 8, 9] 
>>> z = zip(a, b) 
>>> z 
<zip object at 0x0000000002342AC8> 
>>> type(z) 
<class 'zip'> 

Các z là một trình lặp (đối tượng) giữ bên trong các trình vòng lặp cho ab.Do việc triển khai chung, z (hoặc lớp zip) không có ý nghĩa để đặt lại các trình vòng lặp thông qua các a hoặc b hoặc bất kỳ trình tự nào. Do đó không có cách nào để đặt lại số z. Cách sạch nhất để giải quyết vấn đề cụ thể của bạn là sao chép danh sách (như bạn đã đề cập trong câu hỏi và Lennart Regebro suggests). Một cách dễ hiểu là sử dụng zip(a, b) hai lần, do đó xây dựng hai z lặp -like mà ứng xử từ đầu cùng một cách:

>>> lst1 = list(zip(a, b)) 
>>> lst2 = list(zip(a, b)) 

Tuy nhiên, điều này không thể được sử dụng thường với kết quả giống hệt. Hãy suy nghĩ về a hoặc b là chuỗi duy nhất được tạo dựa trên một số điều kiện hiện tại (nói nhiệt độ đọc từ một số nhiệt kế).

Các vấn đề liên quan