2009-04-02 31 views
12

Trong Python giao diện của một iterable là một tập con của iterator interface. Điều này có lợi thế là trong nhiều trường hợp, chúng có thể được điều trị theo cách tương tự. Tuy nhiên, có một sự khác biệt quan trọng về ngữ nghĩa giữa hai, vì một đối tượng có thể lặp lại __iter__ trả về một đối tượng trình lặp mới và không chỉ là self. Làm thế nào tôi có thể kiểm tra rằng một iterable thực sự là một iterable và không phải là một iterator? Về mặt khái niệm, tôi hiểu các vòng lặp có thể là các bộ sưu tập, trong khi một trình vòng lặp chỉ quản lý phép lặp (tức là theo dõi vị trí) nhưng không phải là một bộ sưu tập.Làm thế nào để biết sự khác nhau giữa một trình lặp và một iterable?

Sự khác biệt là ví dụ quan trọng khi muốn lặp lại nhiều lần. Nếu một vòng lặp được đưa ra thì vòng lặp thứ hai sẽ không hoạt động kể từ khi trình vòng lặp đã được sử dụng hết và trực tiếp tăng lên StopIteration.

Thật hấp dẫn để thử nghiệm phương thức next, nhưng điều này có vẻ nguy hiểm và không hiểu sao. Tôi có nên kiểm tra xem vòng lặp thứ hai có trống không?

Có cách nào để thực hiện một thử nghiệm như vậy theo một cách thức sâu sắc hơn không? Tôi biết rằng âm thanh này giống như một trường hợp cổ điển của LBYL chống lại EAFP, vì vậy có lẽ tôi nên bỏ cuộc? Hay tôi đang thiếu một cái gì đó?

Edit: S. Lott nói trong câu trả lời của mình bên dưới rằng đây chủ yếu là một vấn đề muốn làm nhiều đi ngang qua iterator, và rằng người ta không nên làm điều này ở nơi đầu tiên. Tuy nhiên, trong trường hợp của tôi dữ liệu là rất lớn và tùy thuộc vào tình hình đã được thông qua nhiều lần để xử lý dữ liệu (có hoàn toàn không có cách nào xung quanh này).

Khả năng lặp lại cũng được cung cấp bởi người dùng và trong trường hợp một lần vượt qua, nó sẽ hoạt động với trình lặp (ví dụ: được tạo bởi trình tạo đơn giản). Nhưng nó sẽ được tốt đẹp để bảo vệ chống lại các trường hợp là một người sử dụng cung cấp chỉ là một iterator khi nhiều vượt qua là cần thiết.

Chỉnh sửa 2: Thực ra đây là ví dụ rất hay cho Abstract Base Classes. Các phương thức __iter__ trong một iterator và một iterable có cùng tên nhưng khác nhau về mặt ngữ nghĩa! Vì vậy, hasattr là vô ích, nhưng isinstance cung cấp một giải pháp sạch.

Trả lời

13
'iterator' if obj is iter(obj) else 'iterable' 
+0

Ồ, đây có vẻ là câu trả lời mà tôi đã tìm kiếm, cảm ơn! Tôi sẽ chờ đợi một chút trước khi chấp nhận nó, trong trường hợp ai đó có thể chỉ ra một vấn đề với điều này. – nikow

+0

Vâng, vấn đề là một cuộc gọi "lãng phí" để obj .__ iter __(), nhưng tôi không thấy cách đáng tin cậy khác để làm điều đó. – vartec

+1

Mặc dù tôi không biết một ví dụ phản đối, điều này không * được bảo đảm * để hoạt động. – tzot

4

Tuy nhiên, có một sự khác biệt ngữ nghĩa quan trọng giữa hai ...

Không thực sự ngữ nghĩa hay quan trọng. Cả hai đều có thể lặp lại - cả hai đều làm việc với một câu lệnh for.

Sự khác biệt là ví dụ quan trọng khi muốn lặp lại nhiều lần.

Khi nào điều này xuất hiện? Bạn sẽ phải cụ thể hơn. Trong những trường hợp hiếm hoi khi bạn cần thực hiện hai bước qua một bộ sưu tập có thể lặp lại, thường có các thuật toán tốt hơn.

Ví dụ: giả sử bạn đang xử lý danh sách. Bạn có thể lặp qua danh sách tất cả những gì bạn muốn. Tại sao bạn bị rối với một trình lặp thay vì lặp lại? Được rồi mà không làm việc.

OK, đây là một. Bạn đang đọc một tệp trong hai lần truyền và bạn cần biết cách đặt lại có thể lặp lại. Trong trường hợp này, đó là một tệp và yêu cầu seek; hoặc đóng cửa và mở lại. Điều đó cảm thấy khốc liệt.Bạn có thể readlines để có danh sách cho phép hai thẻ không phức tạp. Vì vậy, đó không phải là cần thiết.

Chờ đã, nếu chúng ta có một tệp lớn đến mức chúng ta không thể đọc hết nó vào bộ nhớ? Và, vì những lý do không rõ ràng, chúng tôi cũng không thể tìm kiếm. Vậy thì sao?

Bây giờ, chúng tôi đang giảm đến mức độ nitty-gritty của hai lần. Ngày đầu tiên vượt qua, chúng tôi tích lũy một cái gì đó. Một chỉ mục hoặc một bản tóm tắt hoặc một cái gì đó. Chỉ mục có tất cả dữ liệu của tệp. Tóm tắt, thường là việc tái cấu trúc dữ liệu. Với một thay đổi nhỏ từ "tóm tắt" thành "cơ cấu lại", chúng tôi đã lưu giữ dữ liệu của tệp trong cấu trúc mới. Trong cả hai trường hợp, chúng tôi không cần tệp - chúng tôi có thể sử dụng chỉ mục hoặc tóm tắt.

Tất cả thuật toán "hai vượt qua" có thể được thay đổi thành một lần của trình lặp gốc hoặc có thể lặp lại và vượt qua lần thứ hai của một cấu trúc dữ liệu khác.

Đây không phải là LYBL hoặc EAFP. Đây là thiết kế thuật toán. Bạn không cần đặt lại trình lặp - YAGNI.


Sửa

Dưới đây là một ví dụ về một iterator/vấn đề iterable. Nó chỉ đơn giản là một thuật toán được thiết kế kém.

it = iter(xrange(3)) 
for i in it: print i,; #prints 1,2,3 
for i in it: print i,; #prints nothing 

Điều này là không đáng kể.

it = range(3) 
for i in it: print i 
for i in it: print i 

"Nhiều lần song song" được cố định không đáng kể. Viết một API mà yêu cầu một lần lặp lại. Và khi ai đó từ chối đọc tài liệu API hoặc từ chối theo dõi tài liệu đó sau khi đọc, nội dung của họ sẽ bị hỏng. Như là nó phải như thế.

Điều kiện "tốt để bảo vệ chống lại vụ việc là người dùng chỉ cung cấp trình lặp khi cần nhiều lần" là cả hai ví dụ về những người viết mã phá vỡ API đơn giản của chúng tôi.

Nếu ai đó đủ điên để đọc nhiều nhất (nhưng không phải tất cả tài liệu API) và cung cấp trình lặp khi có thể lặp lại yêu cầu, bạn cần tìm người này và dạy họ (1) cách đọc tất cả Tài liệu API và (2) theo tài liệu API.

Vấn đề "bảo vệ" không thực tế lắm. Những lập trình viên điên rồ này rất hiếm. Và trong một vài trường hợp khi phát sinh, bạn biết họ là ai và có thể giúp họ.


Chỉnh sửa 2

Các "chúng ta phải đọc cùng một cấu trúc nhiều lần" thuật toán là một vấn đề cơ bản.

Không làm điều này.

for element in someBigIterable: 
    function1(element) 
for element in someBigIterable: 
    function2(element) 
... 

Thực hiện việc này thay thế.

for element in someBigIterable: 
    function1(element) 
    function2(element) 
    ... 

Hoặc, hãy cân nhắc điều gì đó như thế này.

for element in someBigIterable: 
    for f in (function1, function2, function3, ...): 
     f(element) 

Trong hầu hết các trường hợp, loại "trục" thuật toán của bạn dẫn đến chương trình có thể dễ dàng tối ưu hóa hơn và có thể cải thiện hiệu suất ròng.

+0

Còn nhiều lần song song? Ví dụ. một số luồng lặp lại trên cùng một bộ sưu tập? Hoặc thậm chí một chủ đề, chẳng hạn như một triển khai ngây thơ dễ tưởng tượng của "bộ sưu tập này có cùng một phần tử hai lần không?". – Edmund

+0

Cảm ơn, tôi đã thêm giải thích cho câu hỏi. Bạn có một điểm hợp lệ, nhưng trong trường hợp của tôi, tôi tin rằng điều này không hiệu quả. – nikow

+0

"đáng kể hiếm". Tôi không đồng ý, các lập trình viên không thể nói lặp lại từ iterator không phải bằng bất kỳ phương tiện hiếm. "bạn biết họ là ai và có thể giúp họ". Đó thường không phải là công việc của bạn, và trong công ty "giúp đỡ họ" sẽ không được nhận thức rất tốt, đặc biệt nếu đó là một bộ phận khác. – vartec

0

Bởi vì gõ vịt của Python,

Bất kỳ đối tượng là iterable nếu nó xác định next()__iter__() phương thức trả về chính nó.

Nếu đối tượng chính nó doesnt có phương pháp next(), các __iter__() có thể trả lại bất kỳ đối tượng, có một phương pháp next()

Bạn có thể tham khảo câu hỏi này để xem Iterability in Python

+0

Hãy thử điều này: lớp A (đối tượng): def __iter __ (self): return iter ([1,2,3]) def next (self): yield 7 – vartec

+0

Thực ra đây là vấn đề đánh máy vịt: nó có thể ẩn một ngữ nghĩa/sự khác biệt về khái niệm. Nó cho phép chúng tôi viết cho tôi trong phạm vi (3) thay vì cho i trong vòng lặp (phạm vi (3)), nhưng có thể gây ra vấn đề tinh tế. – nikow

+0

Xin lỗi, tôi đã không chính xác nhận điểm? Có gì đó không đúng? –

2
import itertools 

def process(iterable): 
    work_iter, backup_iter= itertools.tee(iterable) 

    for item in work_iter: 
     # bla bla 
     if need_to_startover(): 
      for another_item in backup_iter: 

Đó cỗ máy thời gian chết tiệt đó Raymond mượn từ Guido…

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