2012-07-06 7 views
35

Tôi đã có một máy phát điện và một chức năng mà tiêu thụ nó:Xử lý một ngoại lệ ném vào một máy phát điện

def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff 

Nếu máy phát điện ném một ngoại lệ, tôi muốn để xử lý mà trong hàm tiêu dùng và sau đó tiếp tục tiêu thụ trình lặp cho đến khi nó cạn kiệt. Lưu ý rằng tôi không muốn có bất kỳ mã xử lý ngoại lệ nào trong trình tạo.

Tôi nghĩ về một cái gì đó như:

reader = read() 
while True: 
    try: 
     item = next(reader) 
    except StopIteration: 
     break 
    except Exception as e: 
     log error 
     continue 
    do_stuff(item) 

nhưng điều này có vẻ khá vụng về với tôi.

Trả lời

38

Khi máy phát điện ném một ngoại lệ, nó sẽ thoát. Bạn không thể tiếp tục tiêu thụ các mục mà nó tạo ra.

Ví dụ:

>>> def f(): 
...  yield 1 
...  raise Exception 
...  yield 2 
... 
>>> g = f() 
>>> next(g) 
1 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in f 
Exception 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 

Nếu bạn kiểm soát mã máy phát điện, bạn có thể xử lý các ngoại lệ bên trong máy phát điện; nếu không, bạn nên cố gắng tránh một ngoại lệ xảy ra.

+1

Cảm ơn! Sự trình diện này là một trường hợp. Bạn có thể xem câu hỏi tiếp theo: http://stackoverflow.com/q/11366892/989121? – georg

5

Đây cũng là điều mà tôi không chắc chắn nếu tôi xử lý chính xác/thanh lịch.

Những gì tôi làm là để yield một Exception từ trình tạo và sau đó tăng lên ở nơi khác. Giống như:

class myException(Exception): 
    def __init__(self, ...) 
    ... 

def g(): 
    ... 
    if everything_is_ok: 
     yield result 
    else: 
     yield myException(...) 

my_gen = g() 
while True: 
    try: 
     n = next(my_gen) 
     if isinstance(n, myException): 
      raise n 
    except StopIteration: 
     break 
    except myException as e: 
     # Deal with exception, log, print, continue, break etc 
    else: 
     # Consume n 

Bằng cách này tôi vẫn tiếp tục Ngoại lệ mà không làm tăng, điều này sẽ khiến chức năng của máy phát ngừng hoạt động. Hạn chế lớn nhất là tôi cần phải kiểm tra kết quả thu được với isinstance tại mỗi lần lặp. Tôi không thích một máy phát điện có thể mang lại kết quả của các loại khác nhau, nhưng sử dụng nó như là một phương sách cuối cùng.

+1

Cảm ơn, điều này tương tự như những gì tôi đã làm (xem [câu trả lời này] (http://stackoverflow.com/questions/11366892/handle-generator-exceptions-in-its-consumer)) – georg

+0

Cảm ơn @georg vì đã chỉ ra câu trả lời đó. Cho ra một 'tuple' với 'Exception' là, tôi nghĩ, một giải pháp tốt hơn. – dojuba

3

Tôi cần phải giải quyết vấn đề này một vài lần và đưa ra câu hỏi này sau khi tìm kiếm những gì người khác đã làm.

Một tùy chọn - sẽ yêu cầu tái cấu trúc mọi thứ một chút - sẽ là throw ngoại lệ trong trình tạo (đến một trình xử lý xử lý lỗi khác) thay vì raise. Dưới đây là những gì có thể trông giống như:

def read(handler): 
    # the handler argument fixes errors/problems separately 
    while something(): 
     try: 
      yield something_else() 
     except Exception as e: 
      handler.throw(e) 
    handler.close() 

def err_handler(): 
    # a generator for processing errors 
    while True: 
     try: 
      yield 
     except Exception1: 
      handle_exc1() 
     except Exception2: 
      handle_exc2() 
     except Exception3: 
      handle_exc3() 
     except Exception: 
      raise 

def process(): 
    handler = err_handler() 
    for item in read(handler): 
     do stuff 

Điều này không phải lúc nào cũng là giải pháp tốt nhất, nhưng nó chắc chắn là một lựa chọn.

EDIT:

Bạn có thể làm cho nó tất cả chỉ là một chút đẹp hơn với trang trí theo cách này (tôi đã không kiểm tra này, nhưng nó cũng làm việc, EDIT: không làm việc, tôi sẽ sửa chữa nó sau này, nhưng ý tưởng là âm thanh):

def handled(handler): 
    """ 
    A decorator that applies error handling to a generator. 

    The handler argument received errors to be handled. 

    Example usage: 

    @handled(err_handler()) 
    def gen_function(): 
     yield the_things() 
    """ 
    def handled_inner(gen_f): 
     def wrapper(*args, **kwargs): 
      g = gen_f(*args, **kwargs) 
      while True: 
       try: 
        yield from g 
       except Exception as e: 
        handler.throw(e) 
     return wrapper 
    return handled_inner 

@handled(err_handler()) 
def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff