2013-10-11 16 views
17

Ví dụ về sử dụng concurrent.futures (backport cho 2.7):Bắt số dòng ban đầu cho ngoại lệ trong concurrent.futures

import concurrent.futures # line 01 
def f(x): # line 02 
    return x * x # line 03 
data = [1, 2, 3, None, 5] # line 04 
with concurrent.futures.ThreadPoolExecutor(len(data)) as executor: # line 05 
    futures = [executor.submit(f, n) for n in data] # line 06 
    for future in futures: # line 07 
     print(future.result()) # line 08 

Output:

1 
4 
9 
Traceback (most recent call last): 
    File "C:\test.py", line 8, in <module> 
    print future.result() # line 08 
    File "C:\dev\Python27\lib\site-packages\futures-2.1.4-py2.7.egg\concurrent\futures\_base.py", line 397, in result 
    return self.__get_result() 
    File "C:\dev\Python27\lib\site-packages\futures-2.1.4-py2.7.egg\concurrent\futures\_base.py", line 356, in __get_result 
    raise self._exception 
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType' 

Chuỗi "...\_base.py", line 356, in __get_result" không Endpoint tôi mong đợi để xem . Có thể nhận được dòng thực sự, nơi ngoại lệ đã được ném? Một cái gì đó như:

File "C:\test.py", line 3, in f 
    return x * x # line 03 

Python3 có vẻ hiển thị số dòng chính xác trong trường hợp này. Tại sao không thể python2.7? Và có cách nào khác không?

+0

Tôi cũng đang tìm kiếm một câu trả lời này câu hỏi. Cảm ơn! – drpoo

Trả lời

9

Tôi nghĩ rằng traceback ngoại lệ ban đầu bị mất trong mã ThreadPoolExecutor. Nó lưu trữ ngoại lệ và sau đó lại tăng lên sau đó. Đây là một giải pháp. Bạn có thể sử dụng mô-đun traceback để lưu trữ thư ngoại lệ ban đầu và truy xuất từ ​​hàm f của bạn thành chuỗi. Sau đó, tăng ngoại lệ với thông báo lỗi này, hiện có chứa số dòng, v.v. của f. Mã chạy f có thể được gói trong một số thử ... trừ khối, sẽ chặn ngoại lệ được nêu ra từ ThreadPoolExecutor và in thư có chứa dấu vết gốc.

Mã bên dưới phù hợp với tôi. Tôi nghĩ rằng giải pháp này là một chút hacky, và muốn có thể khôi phục lại traceback ban đầu, nhưng tôi không chắc chắn nếu điều đó là có thể.

import concurrent.futures 
import sys,traceback 


def f(x): 
    try: 
     return x * x 
    except Exception, e: 
     tracebackString = traceback.format_exc(e) 
     raise StandardError, "\n\nError occurred. Original traceback is\n%s\n" %(tracebackString) 



data = [1, 2, 3, None, 5] # line 10 

with concurrent.futures.ThreadPoolExecutor(len(data)) as executor: # line 12 
    try: 
     futures = [executor.submit(f, n) for n in data] # line 13 
     for future in futures: # line 14 
      print(future.result()) # line 15 
    except StandardError, e: 
     print "\n" 
     print e.message 
     print "\n" 

này cung cấp cho các đầu ra sau trong python2.7:

1 
4 
9 




Error occurred. Original traceback is 
Traceback (most recent call last): 
File "thread.py", line 8, in f 
    return x * x 
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType' 

Lý do mã ban đầu của bạn cho vị trí bên phải khi chạy bằng Python 3 và không 2,7 là bằng Python 3 trường hợp ngoại lệ mang traceback dưới dạng thuộc tính và khi tăng lại ngoại lệ, truy nguyên được mở rộng thay vì thay thế. Ví dụ dưới đây minh họa điều này:

def A(): 
    raise BaseException("Fish") 

def B(): 
    try: 
     A() 
    except BaseException as e: 
     raise e 

B() 

Tôi chạy này trong python 2.7python 3.1. Trong 2,7 đầu ra như sau:

Traceback (most recent call last): 
    File "exceptions.py", line 11, in <module> 
    B() 
    File "exceptions.py", line 9, in B 
    raise e 
BaseException: Fish 

ví dụ thực tế là ngoại lệ ban đầu được ném từ Một không được ghi lại trong đầu ra cuối cùng. Khi tôi chạy với python 3.1 tôi có được điều này:

Traceback (most recent call last): 
    File "exceptions.py", line 11, in <module> 
    B() 
    File "exceptions.py", line 9, in B 
    raise e 
    File "exceptions.py", line 7, in B 
    A() 
    File "exceptions.py", line 3, in A 
    raise BaseException("Fish") 
BaseException: Fish 

mà là tốt hơn. Nếu tôi thay thế raise e chỉ với raise trong khối ngoại trừ trong B, thì python2.7 cung cấp cho truy xuất hoàn chỉnh. Tôi đoán là khi quay lại mô-đun này cho python2.7 sự khác biệt trong tuyên truyền ngoại lệ đã bị bỏ qua.

+0

Cảm ơn bạn đã giải thích chi tiết và xin lỗi vì đã chấp nhận trễ. – djeendo

14

Tôi đã ở trong tình trạng tương tự của bạn và tôi thực sự cần thiết để có traceback của các trường hợp ngoại lệ lớn lên. Tôi đã có thể phát triển giải pháp thay thế này bao gồm việc sử dụng lớp con sau của ThreadPoolExecutor.

import sys 
import traceback 
from concurrent.futures import ThreadPoolExecutor 

class ThreadPoolExecutorStackTraced(ThreadPoolExecutor): 

    def submit(self, fn, *args, **kwargs): 
     """Submits the wrapped function instead of `fn`""" 

     return super(ThreadPoolExecutorStackTraced, self).submit(
      self._function_wrapper, fn, *args, **kwargs) 

    def _function_wrapper(self, fn, *args, **kwargs): 
     """Wraps `fn` in order to preserve the traceback of any kind of 
     raised exception 

     """ 
     try: 
      return fn(*args, **kwargs) 
     except Exception: 
      raise sys.exc_info()[0](traceback.format_exc()) # Creates an 
                  # exception of the 
                  # same type with the 
                  # traceback as 
                  # message 

Nếu bạn sử dụng lớp này và chạy các đoạn mã sau:

def f(x): 
    return x * x 

data = [1, 2, 3, None, 5] 
with ThreadPoolExecutorStackTraced(max_workers=len(data)) as executor: 
    futures = [executor.submit(f, n) for n in data] 
    for future in futures: 
     try: 
      print future.result() 
     except TypeError as e: 
      print e 

đầu ra sẽ là một cái gì đó như:

1 
4 
9 
Traceback (most recent call last): 
    File "future_traceback.py", line 17, in _function_wrapper 
    return fn(*args, **kwargs) 
    File "future_traceback.py", line 24, in f 
    return x * x 
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType' 

25 

Vấn đề là trong việc sử dụng sys.exc_info() bởi futures thư viện. Từ các tài liệu :

Hàm này trả về một tuple trong ba giá trị mà cung cấp thông tin về các ngoại lệ đó là hiện đang được xử lý. [...] Nếu không có ngoại lệ nào được xử lý ở bất kỳ nơi nào trên ngăn xếp, một bộ chứa ba giá trị None là được trả lại. Nếu không, các giá trị trả lại là (loại, giá trị, traceback). Ý nghĩa của chúng là: loại được loại ngoại lệ của ngoại lệ được xử lý (một đối tượng lớp); giá trị được ngoại lệ tham số (giá trị được liên kết của nó hoặc đối số thứ hai để tăng, mà luôn luôn là một cá thể lớp nếu loại ngoại lệ là một đối tượng lớp); traceback được một đối tượng traceback đóng gói ngăn xếp cuộc gọi tại thời điểm xảy ra ngoại lệ ban đầu.

Bây giờ, nếu bạn nhìn vào mã nguồn của futures bạn có thể nhìn thấy bằng cách tự hỏi tại sao traceback là mất: khi một ngoại lệ đặt ra và nó được thiết lập để các đối tượng Future chỉ sys.exc_info()[1] được thông qua. Xem:

https://code.google.com/p/pythonfutures/source/browse/concurrent/futures/thread.py (L: 63) https://code.google.com/p/pythonfutures/source/browse/concurrent/futures/_base.py (L: 356)

Vì vậy, để tránh mất traceback, bạn phải lưu nó ở đâu đó. Cách giải quyết của tôi là để bọc chức năng để gửi vào một trình bao bọc mà chỉ có nhiệm vụ là bắt tất cả các loại ngoại lệ và để tăng ngoại lệ cùng loại có thông điệp là truy nguyên. Bằng cách này, khi một ngoại lệ được nâng lên, nó sẽ bị bắt và bị reraised bởi trình bao bọc, sau đó khi sys.exc_info()[1] được gán cho ngoại lệ đối tượng Future, truy nguyên sẽ không bị mất.

2

Lấy cảm hứng từ câu trả lời đầu tiên, ở đây nó là như một trang trí:

import functools 
import traceback 


def reraise_with_stack(func): 

    @functools.wraps(func) 
    def wrapped(*args, **kwargs): 
     try: 
      return func(*args, **kwargs) 
     except Exception as e: 
      traceback_str = traceback.format_exc(e) 
      raise StandardError("Error occurred. Original traceback " 
           "is\n%s\n" % traceback_str) 

    return wrapped 

Chỉ cần áp dụng trang trí vào chức năng thực hiện:

@reraise_with_stack 
def f(): 
    pass 
Các vấn đề liên quan