2012-10-08 32 views
39

Lưu ý: Tôi chưa cố gắng tái tạo sự cố được mô tả bên dưới trong Windows hoặc với các phiên bản của Python khác với 2.7.3.Làm cách nào để tắt tiếng "sys.excepthook bị thiếu"?

Cách đáng tin cậy nhất để gợi ra những vấn đề trong câu hỏi là ống đầu ra của kịch bản thử nghiệm sau đây thông qua : (dưới bash):

try: 
    for n in range(20): 
     print n 
except: 
    pass 

Ie:

% python testscript.py | : 
close failed in file object destructor: 
sys.excepthook is missing 
lost sys.stderr 

Câu hỏi của tôi là :

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/ bash)?

(Khi tập lệnh thử nghiệm hiển thị, lỗi không thể bị mắc kẹt với một số try-except.)

Ví dụ trên là thừa nhận, có độ nhân tạo cao, nhưng tôi gặp phải vấn đề tương tự đôi khi khi đầu ra của tập lệnh của tôi được dẫn qua một số bên thứ 3 phần mềm.

Thông báo lỗi chắc chắn là vô hại, nhưng nó gây rối cho người dùng cuối, vì vậy tôi muốn im lặng nó.

CHỈNH SỬA: Tập lệnh sau, khác với tập lệnh gốc ở trên chỉ định nghĩa lại sys.excepthook, hoạt động chính xác như được chỉ định ở trên.

import sys 
STDERR = sys.stderr 
def excepthook(*args): 
    print >> STDERR, 'caught' 
    print >> STDERR, args 

sys.excepthook = excepthook 

try: 
    for n in range(20): 
     print n 
except: 
    pass 

Trả lời

60

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/ bash)?

Bạn sẽ cần phải ngăn chặn kịch bản từ viết bất cứ điều gì để chuẩn đầu ra. Điều đó có nghĩa là xóa mọi tuyên bố print và mọi việc sử dụng sys.stdout.write, cũng như bất kỳ mã nào gọi cho chúng.

Lý do điều này xảy ra là bạn đang tạo ra một lượng không xuất ra từ tập lệnh Python của bạn đến một thứ không bao giờ đọc từ đầu vào tiêu chuẩn. Đây không phải là duy nhất cho lệnh :; bạn có thể nhận được kết quả tương tự bằng đường ống cho bất kỳ lệnh mà không đọc đầu vào tiêu chuẩn, chẳng hạn như

python testscript.py | cd . 

Hoặc cho một ví dụ đơn giản hơn, hãy xem xét một kịch bản không có gì printer.py chứa hơn

print 'abcde' 

Sau đó,

python printer.py | python printer.py 

sẽ tạo ra lỗi tương tự.

Khi bạn đưa đầu ra của chương trình này sang chương trình khác, đầu ra được tạo ra bởi chương trình ghi được sao lưu trong bộ đệm và đợi chương trình đọc yêu cầu dữ liệu đó từ bộ đệm. Miễn là bộ đệm là nonempty, bất kỳ nỗ lực để đóng đối tượng tập tin văn bản được cho là thất bại với một lỗi. Đây là nguyên nhân gốc rễ của các thư bạn đang xem.

Mã cụ thể kích hoạt lỗi trong cài đặt ngôn ngữ C của Python, điều này giải thích tại sao bạn không thể bắt nó bằng khối try/except: nó chạy sau khi nội dung tập lệnh của bạn đã xử lý xong. Về cơ bản, trong khi Python tự tắt, nó cố gắng để đóng stdout, nhưng điều đó không thành công vì vẫn có đầu ra đệm chờ để được đọc. Vì vậy, Python cố gắng báo cáo lỗi này như bình thường, nhưng sys.excepthook đã bị xóa như là một phần của thủ tục hoàn tất, do đó không thành công. Python sau đó cố gắng để in một tin nhắn đến sys.stderr, nhưng điều đó đã được deallocated như vậy một lần nữa, nó không thành công. Lý do bạn thấy các thông báo trên màn hình là mã Python có chứa một số ngẫu nhiên fprintf để ghi ra một số đầu ra cho con trỏ tệp trực tiếp, ngay cả khi đối tượng đầu ra của Python không tồn tại.

Chi tiết kỹ thuật

Đối với những người quan tâm đến các chi tiết của quy trình này, chúng ta hãy nhìn vào chuỗi tắt thông dịch viên của Python, được thực hiện trong Py_Finalize function của pythonrun.c.

  1. Sau khi gọi móc thoát và tắt luồng, mã quyết toán gọi PyImport_Cleanup để hoàn tất và deallocate tất cả các mô-đun đã nhập. Tác vụ tiếp theo được thực hiện bởi hàm này là removing the sys module, chủ yếu bao gồm gọi _PyModule_Clear để xóa tất cả các mục trong từ điển của mô-đun - bao gồm, đặc biệt, đối tượng luồng chuẩn (đối tượng Python) chẳng hạn như stdoutstderr.
  2. Khi giá trị được xóa khỏi từ điển hoặc được thay thế bằng một giá trị mới, its reference count is decremented sử dụng the Py_DECREF macro. Các đối tượng có số tham chiếu bằng 0 sẽ trở thành đủ điều kiện cho deallocation. Vì mô-đun sys giữ các tham chiếu còn lại cuối cùng đối với các đối tượng luồng chuẩn, khi các tham chiếu đó không được đặt bởi _PyModule_Clear, chúng sẽ sẵn sàng được deallocated.
  3. Việc phân bổ đối tượng tệp Python được thực hiện bởi the file_dealloc function trong fileobject.c. đầu tiên invokes the Python file object's close method này bằng cách sử dụng các aptly tên close_the_file function:

    ret = close_the_file(f); 
    

    Đối với một đối tượng tập tin tiêu chuẩn, close_the_file(f)delegates to the C fclose function, đặt một điều kiện lỗi nếu vẫn còn dữ liệu được ghi vào con trỏ tập tin. file_dealloc sau đó kiểm tra cho rằng tình trạng lỗi và in thông điệp đầu tiên bạn thấy:

    if (!ret) { 
        PySys_WriteStderr("close failed in file object destructor:\n"); 
        PyErr_Print(); 
    } 
    else { 
        Py_DECREF(ret); 
    } 
    
  4. Sau khi in thông điệp rằng, Python sau đó cố gắng để hiển thị các ngoại lệ sử dụng PyErr_Print. Điều đó ủy quyền cho PyErr_PrintEx và là một phần của chức năng, PyErr_PrintEx các nỗ lực truy cập máy in ngoại lệ Python từ sys.excepthook.

    hook = PySys_GetObject("excepthook"); 
    

    Điều này sẽ ổn nếu được thực hiện trong quá trình bình thường của chương trình Python, nhưng trong trường hợp này, sys.excepthook đã bị xóa. Kiểm tra lỗi cho tình trạng lỗi này và in thông báo thứ hai dưới dạng thông báo.

    if (hook && hook != Py_None) { 
        ... 
    } else { 
        PySys_WriteStderr("sys.excepthook is missing\n"); 
        PyErr_Display(exception, v, tb); 
    } 
    
  5. Sau khi thông báo cho chúng tôi về thiếu excepthook, Python sau đó rơi trở lại để in các thông tin ngoại lệ sử dụng PyErr_Display, đó là phương pháp mặc định để hiển thị một vết đống. Điều đầu tiên mà chức năng này thực hiện là thử truy cập sys.stderr.

    PyObject *f = PySys_GetObject("stderr"); 
    

    Trong trường hợp này, không hoạt động vì sys.stderr đã bị xóa và không thể truy cập được. Vì vậy, mã gọi trực tiếp fprintf để gửi thư thứ ba tới luồng lỗi chuẩn C.

    if (f == NULL || f == Py_None) 
        fprintf(stderr, "lost sys.stderr\n"); 
    

Điều thú vị là, hành vi này là một chút khác nhau trong Python 3.4 trở lên vì các thủ tục quyết toán tại explicitly flushes the standard output and error streams trước module BUILTIN sẽ bị xóa. Bằng cách này, nếu bạn có dữ liệu đang chờ để được viết, bạn sẽ gặp lỗi báo hiệu rõ ràng rằng tình trạng đó, chứ không phải là lỗi "ngẫu nhiên" trong thủ tục hoàn tất thông thường. Ngoài ra, nếu bạn chạy

python printer.py | python printer.py 

sử dụng Python 3.4 (sau khi đặt dấu ngoặc đơn trên báo cáo kết quả print tất nhiên), bạn không nhận được bất kỳ lỗi nào cả. Tôi cho rằng lời gọi thứ hai của Python có thể tiêu thụ đầu vào tiêu chuẩn vì một lý do nào đó, nhưng đó là một vấn đề hoàn toàn riêng biệt.


Thực ra, đó là lời nói dối. Cơ chế nhập của Python caches a copy of each imported module's dictionary, không được phát hành cho đến khi _PyImport_Fini chạy, later in the implementation of Py_Finalize khi tham chiếu cuối cùng đến các đối tượng luồng chuẩn biến mất. Khi số tham chiếu đạt đến số không, hãy Py_DECREF deallocates các đối tượng ngay lập tức. Nhưng tất cả những gì quan trọng đối với câu trả lời chính là các tham chiếu được loại bỏ khỏi từ điển của mô-đun sys và sau đó được deallocated đôi khi sau đó.

Một lần nữa, điều này là do từ điển của mô-đun sys bị xóa hoàn toàn trước khi bất kỳ thứ gì thực sự được phân bổ, nhờ cơ chế bộ nhớ đệm thuộc tính. Bạn có thể chạy Python với tùy chọn -vv để xem tất cả các thuộc tính của mô-đun không được đặt trước khi bạn nhận được thông báo lỗi về việc đóng con trỏ tệp.

Hành vi cụ thể này là phần duy nhất không có ý nghĩa trừ khi bạn biết về cơ chế bộ nhớ đệm thuộc tính được đề cập trong chú thích trước.

+0

Một lời giải thích rất rõ ràng và ngắn gọn về một cái gì đó mà bình thường sẽ có tôi nắm bắt cho một bể oxy (chết đuối. Cần. Hơn. Không khí!), Cảm ơn! –

+0

Trong trường hợp đầu ra của tập lệnh python là cần thiết bởi các chương trình khác, ví dụ: grep, có cách nào tốt hơn để thực hiện hơn 'python printer.py | grep "abc"? –

+0

@MarkZ. Thành thật mà nói, giải pháp tốt nhất là không cho đầu ra của tập lệnh Python vào một chương trình không đọc nó - tức là tránh toàn bộ tình huống khiến câu hỏi này xuất hiện ngay từ đầu. Nếu không thể vì một lý do lạ nào đó, bạn có thể thực hiện một tùy chọn dòng lệnh như '--quiet' hoặc' --silent' sẽ chặn tất cả đầu ra từ tập lệnh Python. –

1

Trong trường hợp của bạn, trường hợp ngoại lệ không thể bị phát hiện bằng cách sử dụng khối try/except. Để bắt ông, chức năng ghi đè sys.excepthook:

import sys 
sys.excepthook = lambda *args: None 

Từ documentation:

sys.excepthook(type, value, traceback)

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

minh họa ví dụ:

import sys 
import logging 

def log_uncaught_exceptions(exception_type, exception, tb): 

    logging.critical(''.join(traceback.format_tb(tb))) 
    logging.critical('{0}: {1}'.format(exception_type, exception)) 

sys.excepthook = log_uncaught_exceptions 
+1

Bạn có *** thử * ** giải pháp này với tập lệnh thử nghiệm mà tôi đã cung cấp? Sau khi tất cả, đó là lý do tôi cung cấp nó ... – kjo

10

Tôi đã tự mình gặp phải vấn đề này và tìm kiếm câu trả lời. Tôi nghĩ rằng một cách giải quyết đơn giản ở đây là để đảm bảo bạn tuôn ra stdio đầu tiên, vì vậy các khối python thay vì thất bại trong khi tắt kịch bản.Ví dụ:

--- a/testscript.py 
+++ b/testscript.py 
@@ -9,5 +9,6 @@ sys.excepthook = excepthook 
try: 
    for n in range(20): 
     print n 
+ sys.stdout.flush() 
except: 
    pass 

Sau đó, với tập lệnh này không có gì xảy ra, vì ngoại lệ (IOError: [Errno 32] Broken pipe) bị chặn bằng try ... ngoại trừ.

$ python testscript.py | : 
$ 
-3

Tôi nhận thấy đây là câu hỏi cũ nhưng tôi đã tìm thấy câu hỏi đó trong tìm kiếm của Google về lỗi. Trong trường hợp của tôi nó là một lỗi mã hóa. Một trong những báo cáo cuối cùng của tôi là:

print "Good Bye" 

Các giải pháp đã được chỉ đơn giản là ấn định cú pháp để:

print ("Good Bye") 

[Raspberry Pi Zero, Python 2.7.9]

+1

Trong Python 2.7.9, không có sự khác biệt giữa việc có dấu ngoặc đơn trong 'in' và không. – DyZ

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