2011-12-22 18 views
9

Khi tôi sử dụng máy phát điện trong vòng lặp for, có vẻ như "biết", khi không có thêm phần tử nào được tạo ra. Bây giờ, tôi phải sử dụng một máy phát điện KHÔNG có vòng lặp for, và sử dụng tiếp theo() bằng tay, để lấy phần tử tiếp theo. Vấn đề của tôi là, làm thế nào để tôi biết, nếu không có nhiều yếu tố hơn?Python 3.x: Kiểm tra xem máy phát có các thành phần còn lại

Tôi chỉ biết: tiếp theo() đặt ra một ngoại lệ (StopIteration), nếu không có gì còn lại, NHƯNG không phải là ngoại lệ một chút quá "nặng" cho một vấn đề đơn giản như vậy? Không có phương thức nào như has_next() hay như vậy?

Những dòng này nên làm cho rõ ràng, những gì tôi có nghĩa là:

#!/usr/bin/python3 

# define a list of some objects 
bar = ['abc', 123, None, True, 456.789] 

# our primitive generator 
def foo(bar): 
    for b in bar: 
     yield b 

# iterate, using the generator above 
print('--- TEST A (for loop) ---') 
for baz in foo(bar): 
    print(baz) 
print() 

# assign a new iterator to a variable 
foobar = foo(bar) 

print('--- TEST B (try-except) ---') 
while True: 
    try: 
     print(foobar.__next__()) 
    except StopIteration: 
     break 
print() 

# assign a new iterator to a variable 
foobar = foo(bar) 

# display generator members 
print('--- GENERATOR MEMBERS ---') 
print(', '.join(dir(foobar))) 

Đầu ra là như sau:

--- TEST A (for loop) --- 
abc 
123 
None 
True 
456.789 

--- TEST B (try-except) --- 
abc 
123 
None 
True 
456.789 

--- GENERATOR MEMBERS --- 
__class__, __delattr__, __doc__, __eq__, __format__, __ge__, __getattribute__, __gt__, __hash__, __init__, __iter__, __le__, __lt__, __name__, __ne__, __new__, __next__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, close, gi_code, gi_frame, gi_running, send, throw 

Nhờ tất cả mọi người, và có một ngày tốt đẹp! :)

Trả lời

5

Hai câu lệnh bạn đã viết giải quyết việc tìm kiếm đầu cuối của trình tạo theo cùng một cách. Lệnh for-loop chỉ đơn giản là .next() cho đến khi ngoại lệ StopIteration được nâng lên và sau đó nó kết thúc.

http://docs.python.org/tutorial/classes.html#iterators

Như vậy tôi không nghĩ rằng chờ đợi cho các ngoại lệ StopIteration là một cách 'nặng' để đối phó với vấn đề này, đó là cách mà máy phát điện được thiết kế để được sử dụng.

16

Đây là một câu hỏi hay. Tôi sẽ cố gắng chỉ cho bạn cách chúng ta có thể sử dụng khả năng nội suy của Python và nguồn mở để có được câu trả lời. Chúng ta có thể sử dụng mô-đun dis để xem trước màn hình và xem cách trình thông dịch CPython triển khai vòng lặp for trên một trình lặp.

>>> def for_loop(iterable): 
...  for item in iterable: 
...   pass # do nothing 
...  
>>> import dis 
>>> dis.dis(for_loop) 
    2   0 SETUP_LOOP    14 (to 17) 
       3 LOAD_FAST    0 (iterable) 
       6 GET_ITER    
     >> 7 FOR_ITER     6 (to 16) 
      10 STORE_FAST    1 (item) 

    3   13 JUMP_ABSOLUTE   7 
     >> 16 POP_BLOCK    
     >> 17 LOAD_CONST    0 (None) 
      20 RETURN_VALUE   

Bit ngon ngọt dường như là mã vạch FOR_ITER. Chúng ta không thể lặn sâu hơn bằng cách sử dụng dis, vì vậy hãy tìm FOR_ITER trong mã nguồn của trình thông dịch CPython. Nếu bạn poke xung quanh, bạn sẽ tìm thấy nó trong Python/ceval.c; bạn có thể xem nó here. Đây là toàn bộ điều:

TARGET(FOR_ITER) 
     /* before: [iter]; after: [iter, iter()] *or* [] */ 
     v = TOP(); 
     x = (*v->ob_type->tp_iternext)(v); 
     if (x != NULL) { 
      PUSH(x); 
      PREDICT(STORE_FAST); 
      PREDICT(UNPACK_SEQUENCE); 
      DISPATCH(); 
     } 
     if (PyErr_Occurred()) { 
      if (!PyErr_ExceptionMatches(
          PyExc_StopIteration)) 
       break; 
      PyErr_Clear(); 
     } 
     /* iterator ended normally */ 
     x = v = POP(); 
     Py_DECREF(v); 
     JUMPBY(oparg); 
     DISPATCH(); 

Bạn có thấy cách hoạt động này không? Chúng tôi cố gắng lấy một mục từ trình vòng lặp; nếu chúng ta thất bại, chúng tôi kiểm tra những gì ngoại lệ được nêu ra. Nếu đó là StopIteration, chúng tôi xóa nó và xem xét trình lặp đã hết.

Vậy vòng lặp for "chỉ biết" khi một trình vòng lặp đã cạn kiệt như thế nào? Trả lời: nó không - nó phải cố gắng và lấy một phần tử. Nhưng tại sao?

Một phần câu trả lời là sự đơn giản. Một phần của vẻ đẹp của việc thực hiện các trình vòng lặp là bạn chỉ phải xác định một hoạt động: lấy phần tử tiếp theo. Nhưng quan trọng hơn, nó làm cho các vòng lặp lười biếng: chúng sẽ chỉ tạo ra các giá trị mà chúng hoàn toàn phải có.

Cuối cùng, nếu bạn đang thực sự thiếu tính năng này, việc thực hiện nó là không quan trọng. Dưới đây là một ví dụ:

class LookaheadIterator: 

    def __init__(self, iterable): 
     self.iterator = iter(iterable) 
     self.buffer = [] 

    def __iter__(self): 
     return self 

    def __next__(self): 
     if self.buffer: 
      return self.buffer.pop() 
     else: 
      return next(self.iterator) 

    def has_next(self): 
     if self.buffer: 
      return True 

     try: 
      self.buffer = [next(self.iterator)] 
     except StopIteration: 
      return False 
     else: 
      return True 


x = LookaheadIterator(range(2)) 

print(x.has_next()) 
print(next(x)) 
print(x.has_next()) 
print(next(x)) 
print(x.has_next()) 
print(next(x)) 
+1

Tôi vừa mới nhận ra mình muốn làm 'dis' vì học 'numpy'. ;) – n611x007

0

Nó không thể biết trước về end-of-iterator trong trường hợp tổng quát, bởi vì mã tùy ý có thể phải chạy để quyết định về kết thúc.Yếu tố đệm có thể giúp tiết lộ mọi thứ với chi phí - nhưng điều này hiếm khi hữu ích.

Trong thực tế, câu hỏi nảy sinh khi một người muốn chỉ lấy một hoặc vài phần tử từ một trình lặp, nhưng không muốn viết mã xử lý ngoại lệ xấu xí đó (như được chỉ ra trong câu hỏi). Thật vậy, nó không phải là pythonic để đưa khái niệm "StopIteration" vào mã ứng dụng bình thường. Và xử lý ngoại lệ trên mức python khá tốn thời gian - đặc biệt khi nó chỉ là lấy một phần tử.

Cách pythonic để xử lý những tình huống tốt nhất là một trong hai sử dụng for .. break [.. else] như:

for x in iterator: 
    do_something(x) 
    break 
else: 
    it_was_exhausted() 

hoặc bằng cách sử dụng được xây dựng trong next() chức năng với mặc định như

x = next(iterator, default_value) 

hoặc sử dụng người giúp đỡ iterator ví dụ từ itertools mô-đun cho đi dây lại những thứ như:

max_3_elements = list(itertools.islice(iterator, 3)) 

Một số lặp Tuy nhiên phơi bày một "dài gợi ý" (PEP424):

>>> gen = iter(range(3)) 
>>> gen.__length_hint__() 
3 
>>> next(gen) 
0 
>>> gen.__length_hint__() 
2 

Lưu ý: iterator.__next__() không nên được sử dụng bởi mã ứng dụng bình thường. Đó là lý do tại sao họ đổi tên nó từ iterator.next() bằng Python2. Và việc sử dụng next() mà không có mặc định không tốt hơn nhiều ...