2017-11-13 12 views
8

Tôi đang làm việc trên tập dữ liệu từ MOOC. Tôi có rất nhiều đoạn mã python3 mà tôi cần để chạy và nhận được kết quả từ đó. Để làm điều này tôi đã viết một kịch bản python lặp lại trên mỗi đoạn mã. Đối với mỗi đoạn mã I:Mã chạy an toàn trong một quy trình, chuyển hướng stdout trong multithreading.Process

  1. Tạo mới StringIO đối tượng
  2. Đặt sys.stdoutsys.stderr để đệm StringIO tôi
  3. Execute đoạn mã trong một đối tượng
  4. threading.thread Tham gia vào chủ đề
  5. Log các kết quả trong bộ đệm chuỗiIOIO
  6. Khôi phục thiết bị xuất chuẩn và tiêu chuẩn cảnh quan

này hoạt động tốt cho mã "đúng", nhưng điều này có vấn đề trong trường hợp khác:

  • Khi mã có một vòng lặp vô hạn, Thread.Join không diệt các sợi. Chủ đề là một chuỗi daemon, vì vậy nó chạy lặng lẽ trong nền cho đến khi vòng lặp của tôi kết thúc.
  • Khi mã có vòng lặp vô hạn với print(), chủ đề bắt đầu ghi đè lên giá trị thực của tôi khi tôi đặt nó trở về mặc định (cách xa bộ đệm StringIO). Điều này gây ô nhiễm cho báo cáo của tôi.

Đây là mã hiện tại của tôi:

def execCode(code, testScript=None): 
    # create file-like string to capture output 
    codeOut = io.StringIO() 
    codeErr = io.StringIO() 

    # capture output and errors 
    sys.stdout = codeOut 
    sys.stderr = codeErr 

    def worker(): 
     exec(code, globals()) 

     if testScript: 
      # flush stdout/stderror 
      sys.stdout.truncate(0) 
      sys.stdout.seek(0) 
      # sys.stderr.truncate(0) 
      # sys.stderr.seek(0) 
      exec(testScript) 

    thread = threading.Thread(target=worker, daemon=True) 
    # thread = Process(target=worker) #, stdout=codeOut, stderr=codeErr) 
    thread.start() 
    thread.join(0.5) # 500ms 

    execError = codeErr.getvalue().strip() 
    execOutput = codeOut.getvalue().strip() 

    if thread.is_alive(): 
     thread.terminate() 
     execError = "TimeError: run time exceeded" 

    codeOut.close() 
    codeErr.close() 

    # restore stdout and stderr 
    sys.stdout = sys.__stdout__ 
    sys.stderr = sys.__stderr__ 

    # restore any overridden functions 
    restoreBuiltinFunctions() 

    if execError: 
     return False, stripOuterException(execError) 
    else: 
     return True, execOutput 

Để xử lý trường hợp này, tôi đã cố gắng sử dụng multithreading.Process và/hoặc contextlib.redirect_stdout để chạy mã trong một quá trình (sau đó tôi có thể gọi process.terminate()) nhưng tôi không có bất kỳ thành công nào trong việc thu thập stdout/stderr.

Vì vậy, câu hỏi của tôi là: Làm thế nào tôi có thể chuyển hướng hoặc chụp stdout/stderr từ một quá trình? Ngoài ra, có cách nào khác tôi có thể đi về cố gắng để chạy và nắm bắt đầu ra của mã tùy ý?

(Và vâng, tôi biết đây là một ý tưởng tồi nói chung; Tôi đang chạy nó trong một máy ảo chỉ trong trường hợp có mã độc trong một nơi nào đó)

phiên bản

Python là 3.5.3


cập nhật

Nó xảy ra với tôi rằng có một chút linh hoạt hơn trong tình huống này. Tôi có một hàm, preprocess(code) chấp nhận việc gửi mã dưới dạng chuỗi và thay đổi nó. Chủ yếu là tôi đã sử dụng nó để hoán đổi giá trị của một số biến bằng cách sử dụng cụm từ thông dụng.

Đây là một thực hiện ví dụ:

def preprocess(code): 
    import re 
    rx = re.compile('earlier_date\s*=\s*.+') 
    code = re.sub(rx, "earlier_date = date(2016, 5, 3)", code) 
    rx = re.compile('later_date\s*=\s*.+') 
    code = re.sub(rx, "later_date = date(2016, 5, 24)", code) 
    return code 

tôi có thể sử dụng chức năng preprocess để giúp chuyển hướng STDOUT

+0

Bạn đã xem 'đăng nhập' chưa? – igrinis

+1

Tôi có một số đăng nhập. Công việc hiện tại của tôi xung quanh là bật đăng nhập khi tôi gặp phải một vòng lặp vô hạn. Tôi sử dụng tính năng ghi nhật ký để theo dõi và xóa đoạn mã vi phạm nhưng đây là quy trình thủ công không thể tự động được (Nếu tôi có thể tự động hóa, tôi sẽ không cần đăng nhập bất kỳ thứ gì, tôi có thể hủy bỏ, xóa bản ghi và tiếp tục) – Zack

+1

Còn về ['subprocess.check_output'] (https://docs.python.org/3/library/subprocess.html#subprocess.check_output)? Bạn có thể gọi 'python -c {snippet}' với nó, hoặc nếu nó dài hơn, chỉ cần viết đoạn mã vào một tệp '.py' tạm thời. 'check_output' (và thực sự, tất cả các hàm khác trong' subprocess') có tham số 'timeout'. –

Trả lời

3

Giao tiếp với quy trình đang chạy không đơn giản trong Python. Đối với một số lý do bạn chỉ có thể làm điều đó một lần trong vòng đời của tiến trình con. Từ kinh nghiệm của tôi, tốt nhất là chạy một luồng bắt đầu một tiến trình và sau khi hết thời gian chờ, nó sẽ kết thúc tiến trình con.

Cái gì như:

def subprocess_with_timeout(cmd, timeout_sec, stdin_data=None): 
    """Execute `cmd` in a subprocess and enforce timeout `timeout_sec` seconds. 

    Send `stdin_data` to the subprocess. 

    Return subprocess exit code and outputs on natural completion of the subprocess. 
    Raise an exception if timeout expires before subprocess completes.""" 
    proc = os.subprocess.Popen(cmd, 
         stdin=subprocess.PIPE, 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE) 
    timer = threading.Timer(timeout_sec, proc.kill) 
    # this will terminate subprocess after timeout 
    timer.start() 

    # you will be blocked here until process terminates (by itself or by timeout death switch) 
    stdoutdata, stderrdata = proc.communicate(stdin_data) 

    if timer.is_alive(): 
     # Process completed naturally - cancel timer and return exit code 
     timer.cancel() 
     return proc.returncode, stdoutdata, stderrdata 
    # Process killed by timer - raise exception 
    raise TimeoutError('Process #%d killed after %f seconds' % (proc.pid, timeout_sec)) 

Vì vậy, hãy chạy một đao phủ ren mà các cuộc gọi cho subprocess_with_timeout. Nó sẽ xử lý các đầu vào và lưu kết quả.

Một ý tưởng khác là sử dụng máy chủ web để thực hiện IPC. Xem this link

0

gì về subprocess.check_output? Bạn có thể gọi python -c {snippet} cùng với nó hoặc nếu nó dài hơn, chỉ cần viết đoạn mã vào tệp .py tạm thời.check_output (và thực sự, tất cả các chức năng khác trong subprocess) có thông số timeout.

Ý tưởng chung là sau đó:

import subprocess 
import sys 

def execCode(code): 
    try: 
     output = subprocess.check_output([sys.executable, '-c', code], 
             stdin=subprocess.PIPE, 
             stderr=subprocess.PIPE, 
             timeout=0.5) 
     return True, output 
    except subprocess.TimeoutExpired as te: 
     return False, 'run time exceeded' 
    except subprocess.CalledProcessError as cpe: 
     return False, cpe.stderr 

Ví dụ chạy trong IPython:

In [18]: execCode('import os\nprint(" ".join(os.listdir()))') 
Out[18]: 
(True, 
b'contents of directory\n') 

In [19]: execCode('import time\ntime.sleep(1)') 
Out[19]: (False, 'run time exceeded') 

In [20]: execCode('import os\nprint("\t".join(os.listdi))') 
Out[20]: 
(False, 
b'Traceback (most recent call last):\n File "<string>", line 2, in <module>\nAttributeError: module \'os\' has no attribute \'listdi\'\n') 

Lưu ý rằng check_output trả về một chuỗi bytes, vì vậy bạn sẽ phải chuyển đổi nó thành str. Hoặc bạn có thể sử dụng tham số encoding của check_output.

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