Bir alt işlemde engellemeyen okuma. Python'da boru


506

Bir alt süreci başlatmak ve çıkış akışına (stdout) bağlanmak için alt işlem modülünü kullanıyorum . Stdout'unda tıkanmasız okumalar yapabilmek istiyorum. .Readline'ı engellememenin veya yayınlamadan önce akışta veri olup olmadığını kontrol etmenin bir yolu var mı .readline? Bunun taşınabilir olmasını veya en azından Windows ve Linux altında çalışmasını istiyorum.

İşte nasıl için şimdilik ( .readlinehiçbir veri varsa , engelleme ):

p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE)
output_str = p.stdout.readline()

14
(Google'dan geliyor mu?) PIPE'lerin arabelleğinden biri dolduğunda ve okunmadığında tüm PIPE'ler kilitlenecektir. stderr doldurulduğunda stdout kilitlenme. Asla okumak istemediğiniz bir BORU geçmeyin.
Nasser Al-Wohaibi

@ NasserAl-Wohaibi, o zaman her zaman dosya oluşturmanın daha iyi olduğu anlamına mı geliyor?
Charlie Parker

merak ettiğim bir şey neden ilk etapta tıkanıyor ... soruyorum çünkü yorumu gördüm:To avoid deadlocks: careful to: add \n to output, flush output, use readline() rather than read()
Charlie Parker

"Tasarım gereği", girdi almayı bekliyor.
Mathieu Pagé

Yanıtlar:


403

fcntl, select, asyncprocBu durumda yardımcı olmayacaktır.

İşletim sisteminden bağımsız olarak bir akışı engellemeden okumak için güvenilir bir yol kullanmaktır Queue.get_nowait():

import sys
from subprocess import PIPE, Popen
from threading  import Thread

try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty  # python 2.x

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()

p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()

# ... do other things here

# read line without blocking
try:  line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
    print('no output yet')
else: # got line
    # ... do something with line

6
Evet bu benim için işe yarıyor, yine de çok kaldırdım. İyi uygulamalar içerir, ancak her zaman gerekli değildir. Python 3.x 2.X uyumlu ve close_fds atlanabilir, yine de çalışır. Ancak her şeyin ne yaptığını unutmayın ve sadece işe yarıyor olsa bile körü körüne kopyalamayın! (Aslında en basit çözüm, bir iş parçacığı kullanmak ve Seb'in yaptığı gibi bir okuma çizgisi yapmaktır, Qeues verileri almanın kolay bir yoludur, diğerleri vardır, iş parçacıkları cevaptır!)
Aki

3
İş parçacığının içinde, iş out.readlineparçacığı ve ana iş parçacığı engelleme çağrısı ve ben her şey devam etmeden önce readline dönene kadar beklemek zorunda. Bunun kolay bir yolu var mı? (Aynı zamanda DB ve şeyler yapan başka bir .py dosyası olan işlemimden birden fazla satır okuyorum)
Justin

3
@Justin: 'out.readline' başka bir iş parçacığında yürüttüğü ana iş parçacığını engellemiyor.
jfs

4
alt süreci kapatmazsam ne olur, örneğin. istisnalar nedeniyle? stdout-okuyucu iş parçacığı ölmeyecek ve ana iş parçasından çıkılsa bile python asılacak değil mi? bir kişi bu sorunu nasıl çözebilir? python 2.x iş parçacıklarının öldürülmesini desteklemez, daha da kötüsü, bunların kesilmesini desteklemez. :( (açıkçası, alt
sürecin


77

Sık sık benzer bir sorun yaşadım; Sık sık yazdığım Python programları, komut satırından (stdin) kullanıcı girişini aynı anda kabul ederken bazı birincil işlevleri yürütme yeteneğine sahip olmalıdır. Kullanıcı girişi işleme işlevselliğini başka bir iş parçacığına yerleştirmek sorunu çözmez, çünkü readline()bloklar ve zaman aşımı yoktur. Birincil işlevsellik tamamlanmışsa ve artık daha fazla kullanıcı girişi için beklemek gerekmiyorsa, genellikle programımın çıkmasını istiyorum, ancak yine de readline()bir satır bekleyen diğer iş parçacığında engelleme yapamıyor . Bu soruna bulduğum bir çözüm, stdin'i fcntl modülünü kullanarak engellenmeyen bir dosya yapmaktır:

import fcntl
import os
import sys

# make stdin a non-blocking file
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

# user input handling thread
while mainThreadIsRunning:
      try: input = sys.stdin.readline()
      except: continue
      handleInput(input)

Bence bu, bu sorunu çözmek için select veya sinyal modüllerini kullanmaktan biraz daha temiz ama daha sonra sadece UNIX'te çalışıyor ...


1
Dokümanlara göre fcntl (), bir dosya tanımlayıcı veya .fileno () yöntemine sahip bir nesne alabilir.
Denilson Sá Maia

10
Jesse'nin cevabı doğru değil. Guido'ya göre, readline engelleme olmayan mod ile düzgün çalışmıyor ve Python 3000'den önce çalışmıyor. Bugs.python.org/issue1175#msg56041 Dosyayı engellemeyen moda ayarlamak için fcntl kullanmak istiyorsanız, alt düzey os.read () yöntemini kullanmanız ve satırları kendiniz ayırmanız gerekir. Fcntl ile satır arabelleğe alma işlemini gerçekleştiren üst düzey çağrılarla karıştırmak sorun istiyor.
anonnn

2
Python 2'de readline kullanımı yanlış görünüyor. Bkz. Anonnn'ın cevabı stackoverflow.com/questions/375427/…
Catalin Iacob

10
Lütfen, meşgul döngüleri kullanmayın. Verileri beklemek için bir zaman aşımı ile anket () kullanın.
Ivo Danihelka

@Stefano nedir buffer_size?
kedi

39

Python 3.4 , eşzamansız IO modülü için yeni geçici API tanıttı .asyncio

Yaklaşım twisted@Byan Ward'ın temelli cevabına benzer - bir protokol tanımlayın ve veri hazır olur olmaz yöntemleri çağrılır:

#!/usr/bin/env python3
import asyncio
import os

class SubprocessProtocol(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        if fd == 1: # got stdout data (bytes)
            print(data)

    def connection_lost(self, exc):
        loop.stop() # end loop.run_forever()

if os.name == 'nt':
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol, 
        "myprogram.exe", "arg1", "arg2"))
    loop.run_forever()
finally:
    loop.close()

Dokümanlardaki "Alt işlem" konusuna bakın .

Koroutin ( / Python 3.5+ sözdizimi ile ) kullanarak bir satırı eşzamansız olarak okumayı sağlayan nesneleriasyncio.create_subprocess_exec() döndüren yüksek düzeyli bir arabirim vardır :ProcessStreamReader.readline()asyncawait

#!/usr/bin/env python3.5
import asyncio
import locale
import sys
from asyncio.subprocess import PIPE
from contextlib import closing

async def readline_and_kill(*args):
    # start child process
    process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)

    # read line (sequence of bytes ending with b'\n') asynchronously
    async for line in process.stdout:
        print("got line:", line.decode(locale.getpreferredencoding(False)))
        break
    process.kill()
    return await process.wait() # wait for the child process to exit


if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

with closing(loop):
    sys.exit(loop.run_until_complete(readline_and_kill(
        "myprogram.exe", "arg1", "arg2")))

readline_and_kill() aşağıdaki görevleri gerçekleştirir:

  • alt süreci başlat, stdout'unu bir boruya yönlendir
  • eşzamanlı olarak alt işlem stdout'undan bir satır oku
  • alt süreci öldür
  • çıkmasını bekleyin

Gerekirse her adım zaman aşımı süresi ile sınırlandırılabilir.


Python 3.4 coroutines kullanarak böyle bir şey denediğimde, ben sadece tüm komut dosyası çalıştıktan sonra çıktı almak. Alt işlem bir çizgi yazdırır yazdırmaz çıktı satırı yazdırmak istiyorum. İşte sahip olduğum şey: pastebin.com/qPssFGep .
flutefreak7

1
@ flutefreak7: arabelleğe alma sorunları geçerli soru ile ilgisiz. Olası çözümler için bağlantıyı takip edin.
jfs

Teşekkürler! Betiğimin sorununu, kullanarak print(text, flush=True)yazdırılan metnin izleyici tarafından hemen kullanılabilir olmasını sağlayarak çözdüm readline. Ben aslında sarmak / izlemek istiyorum Fortran tabanlı yürütülebilir ile test ettiğimde, çıkış tampon bellek değil, bu yüzden beklendiği gibi davranır.
16:34

Alt işlemin devam etmesine izin vermek ve daha fazla okuma / yazma işlemi gerçekleştirmek mümkün mü? readline_and_kill, ikinci komut dosyanızda, subprocess.comunicatebir okuma / yazma işleminden sonra işlemi sonlandırması gibi çalışır. Ayrıca stdout, alt işlemin engellemeyen olarak işlediği tek bir boru kullandığınızı görüyorum . Her ikisini de kullanmaya çalışıyorum stdoutve stderr sonunda engellememi buluyorum .
Carel

@ Cevaptaki kodu, yanıtta açıkça belirtildiği gibi çalışır. İstenirse başka davranışlar uygulamak da mümkündür. Her iki boru da eşit şekilde bloke olmaz , her iki borudan aynı anda nasıl okunacağına dair bir örnek .
jfs

19

Asyncproc modülünü deneyin . Örneğin:

import os
from asyncproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll != None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

Modül, S.Lott tarafından önerilen tüm diş çekme işlemlerini gerçekleştirir.


1
Kesinlikle harika. Ham alt işlem modülünden çok daha kolay. Ubuntu'da benim için mükemmel çalışıyor.
Cerin

12
asyncproc pencerelerde çalışmaz ve pencereler os'i desteklemez. WOHHANGANG :-(
Bryan Oakley

26
asyncproc GPL'dir, bu da kullanımını sınırlandırır :-(
Bryan Oakley

Teşekkürler. Küçük bir şey: Görünüşe göre sekmeleri asyncproc.py'deki 8 boşlukla değiştirmenin yolu :)
benjaoming

Asyncproc modülü aracılığıyla başlattığınız işlemin dönüş kodunu alabileceğiniz gibi görünmüyor; sadece ürettiği çıktı.
grayaii

17

Bunu Twisted'da kolayca yapabilirsiniz . Mevcut kod tabanınıza bağlı olarak, bu kullanımı o kadar kolay olmayabilir, ancak bükülmüş bir uygulama oluşturuyorsanız, bunun gibi şeyler neredeyse önemsiz hale gelir. Bir ProcessProtocolsınıf oluşturun ve outReceived()yöntemi geçersiz kılın . Bükülmüş (kullanılan reaktöre bağlı olarak) genellikle select()farklı dosya tanımlayıcılarından (genellikle ağ soketleri) gelen verileri işlemek için geri çağrıları yüklü büyük bir döngüdür. Yani outReceived()yöntem basitçe gelen verileri işlemek için bir geri arama kurmaktır STDOUT. Bu davranışı gösteren basit bir örnek şöyledir:

from twisted.internet import protocol, reactor

class MyProcessProtocol(protocol.ProcessProtocol):

    def outReceived(self, data):
        print data

proc = MyProcessProtocol()
reactor.spawnProcess(proc, './myprogram', ['./myprogram', 'arg1', 'arg2', 'arg3'])
reactor.run()

Twisted dokümantasyon Bu konuda bazı faydalı bilgiler var.

Tüm uygulamanızı Twisted etrafında oluşturursanız, yerel veya uzak diğer süreçlerle asenkron iletişim kurar, bu gerçekten zariftir. Öte yandan, programınız Twisted üzerine kurulmamışsa, bu gerçekten yararlı olmayacaktır. Umarım bu, uygulamanız için geçerli olmasa bile diğer okuyucular için yararlı olabilir.


iyi değil. dokümanlaraselect göre dosya tanımlayıcıları olan pencerelerde çalışmamalıdır
n611x007 9:13

2
@naxa Bahsettiği kişi seninle aynı olduğunu sanmıyorum select(). Bunu varsayıyorum çünkü Twistedpencerelerde çalışıyor ...
notbad.jpeg


1
"Bükülmüş (kullanılan reaktöre bağlı olarak) genellikle sadece büyük bir seçme () döngüsüdür" arasında seçim yapabileceğiniz birkaç reaktör olduğu anlamına gelir. select()Bir Unixlerin ve Unix sever en taşınabilir biridir, ancak iki reaktör Windows için geçerli de vardır: twistedmatrix.com/documents/current/core/howto/...
clacke

14

Seç ve oku (1) düğmesini kullanın.

import subprocess     #no new requirements
def readAllSoFar(proc, retVal=''): 
  while (select.select([proc.stdout],[],[],0)[0]!=[]):   
    retVal+=proc.stdout.read(1)
  return retVal
p = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE)
while not p.poll():
  print (readAllSoFar(p))

Readline () için - like:

lines = ['']
while not p.poll():
  lines = readAllSoFar(p, lines[-1]).split('\n')
  for a in range(len(lines)-1):
    print a
lines = readAllSoFar(p, lines[-1]).split('\n')
for a in range(len(lines)-1):
  print a

6
iyi değil. dokümanlaraselect göre dosya tanımlayıcıları olan pencerelerde çalışmamalıdır
n611x007 9:13

AMAN TANRIM. Megabayt okuyun veya her seferinde bir karakter gigabayt okuyun ... bu uzun zamandır gördüğüm en kötü fikir ... bahsetmeye gerek yok, bu kod işe yaramaz, çünkü proc.stdout.read()argüman ne kadar küçük olursa olsun engelleme çağrısı.
wvxvw

OSError: [WinError 10093] Either the application has not called WSAStartup, or WSAStartup failed
nmz787

8

Bir çözüm, işlemi okumanızı gerçekleştirmek için başka bir işlem yapmak veya zaman aşımı ile işlemin bir iş parçacığını yapmaktır.

Zaman aşımı işlevinin iş parçacığı sürümü:

http://code.activestate.com/recipes/473878/

Ancak, stdout'u gelirken okumalısınız? Başka bir çözüm, çıktıyı bir dosyaya dökmek ve işlemin p.wait () kullanarak bitmesini beklemek olabilir .

f = open('myprogram_output.txt','w')
p = subprocess.Popen('myprogram.exe', stdout=f)
p.wait()
f.close()


str = open('myprogram_output.txt','r').read()

recpie'nin gibi görünüyor iş parçacığı zaman aşımından sonra ve öldürmek alt süreci (sg. aksi takdirde bu bağlamda ilgisiz) öldürme yeteneğine bağlı olarak okuyor (yapabilmeniz gereken bir şey, ancak yapamamanız durumunda ..) .
n611x007

7

Feragatname: Bu sadece kasırga için geçerlidir

Bunu, fd'nin engellemeyi kaldıracak şekilde ayarlayıp ardından geri çağrıları kaydetmek için ioloop'u kullanabilirsiniz. Bunu tornado_subprocess adlı bir yumurtada paketledim ve PyPI ile kurabilirsiniz:

easy_install tornado_subprocess

şimdi böyle bir şey yapabilirsiniz:

import tornado_subprocess
import tornado.ioloop

    def print_res( status, stdout, stderr ) :
    print status, stdout, stderr
    if status == 0:
        print "OK:"
        print stdout
    else:
        print "ERROR:"
        print stderr

t = tornado_subprocess.Subprocess( print_res, timeout=30, args=[ "cat", "/etc/passwd" ] )
t.start()
tornado.ioloop.IOLoop.instance().start()

bunu bir RequestHandler ile de kullanabilirsiniz

class MyHandler(tornado.web.RequestHandler):
    def on_done(self, status, stdout, stderr):
        self.write( stdout )
        self.finish()

    @tornado.web.asynchronous
    def get(self):
        t = tornado_subprocess.Subprocess( self.on_done, timeout=30, args=[ "cat", "/etc/passwd" ] )
        t.start()

Güzel özellik için teşekkürler! Sadece açıklığa kavuşturmak için, neden threading.Threadtıkanmayan yeni süreçler oluşturmak için kullanamıyoruz ? on_messageTornado websocket örneğinde kullandım ve işi iyi yaptı.
VisioN

1
Kasırgada diş çekme çoğunlukla önerilmez. küçük, kısa çalışma fonksiyonları için uygundur. Burada okuyabilirsiniz: stackoverflow.com/questions/7846323/tornado-web-and-threads github.com/facebook/tornado/wiki/Threading-and-concurrency
Vukasin Toroman

@VukasinToroman beni bununla gerçekten kurtardın. tornado_subprocess modülü için çok teşekkür ederim :)
James Gentes

bu pencerelerde çalışıyor mu? ( selectdosya tanıtıcıları ile değil )
n611x007 9:13

Bu lib selectçağrıyı kullanmaz . Bunu Windows altında denemedim ama lib fcntlmodülü kullandığından muhtemelen sorun yaşarsınız . Kısacası: hayır bu muhtemelen Windows altında çalışmaz.
Vukasin Toroman

6

Mevcut çözümler benim için işe yaramadı (ayrıntılar aşağıdadır). Sonunda işe yarayan, read (1) kullanarak readline uygulamaktı ( bu cevaba dayanarak ). İkincisi engellemez:

from subprocess import Popen, PIPE
from threading import Thread
def process_output(myprocess): #output-consuming thread
    nextline = None
    buf = ''
    while True:
        #--- extract line using read(1)
        out = myprocess.stdout.read(1)
        if out == '' and myprocess.poll() != None: break
        if out != '':
            buf += out
            if out == '\n':
                nextline = buf
                buf = ''
        if not nextline: continue
        line = nextline
        nextline = None

        #--- do whatever you want with line here
        print 'Line is:', line
    myprocess.stdout.close()

myprocess = Popen('myprogram.exe', stdout=PIPE) #output-producing process
p1 = Thread(target=process_output, args=(dcmpid,)) #output-consuming thread
p1.daemon = True
p1.start()

#--- do whatever here and then kill process and thread if needed
if myprocess.poll() == None: #kill process; will automatically stop thread
    myprocess.kill()
    myprocess.wait()
if p1 and p1.is_alive(): #wait for thread to finish
    p1.join()

Mevcut çözümler neden işe yaramadı:

  1. Readline gerektiren çözümler (Kuyruk tabanlı olanlar dahil) her zaman engellenir. Readline'ı çalıştıran iş parçacığını öldürmek zor (imkansız?). Yalnızca onu oluşturan süreç bittiğinde öldürülür, ancak çıktı üreten süreç öldürüldüğünde öldürülmez.
  2. Anonnn işaret ettiği gibi, düşük seviyeli fcntl ile yüksek seviyeli readline çağrılarının karıştırılması düzgün çalışmayabilir.
  3. Select.poll () öğesini kullanmak düzgün, ancak Windows'ta python belgelerine göre çalışmaz.
  4. Üçüncü taraf kitaplıkların kullanılması bu görev için aşırı yüklü gibi görünüyor ve ek bağımlılıklar ekliyor.

1
1. q.get_nowait()benim cevap blok, hiç, bu kullanma noktası engellememelidir. 2. Readline ( enqueue_output()işlev ) yürüten iş parçacığı, örneğin çıktı üretme işleminin öldürüldüğü durum da dahil olmak üzere EOF üzerinde çıkar. Eğer öyle olmadığına inanıyorsanız; lütfen, aksini gösteren tam bir minimal kod örneği verin (belki yeni bir soru olarak ).
jfs

1
@sebastian Minimal bir örnek bulmaya çalışmak için bir saat ya da daha fazla zaman harcadım. Sonunda cevabınızın tüm davaları ele aldığını kabul etmeliyim. Sanırım bu benim için daha önce işe yaramadı çünkü çıktı üreten süreci öldürmeye çalışırken zaten öldürülmüştü ve hata ayıklaması zor bir hata verdi. Saat iyi geçti, çünkü minimal bir örnek oluştururken daha basit bir çözüm bulabilirdim.
Vikram Pudi

Daha basit çözümü de gönderebilir misiniz? :) (Sebastian'dan
farklıysa

@ danger89: Sanırım dcmpid = myprocess.
ViFI

Read () çağrısından sonra (
True'dan

6

Kodum, kısmi satırlar da dahil olmak üzere ASAP alt işleminden her çıktıyı yakalamak için kullanılır. Aynı anda pompalanır ve stdout ve stderr neredeyse doğru sıradadır.

Python 2.7 Linux ve Windows üzerinde test edilmiş ve doğru bir şekilde çalışılmıştır.

#!/usr/bin/python
#
# Runner with stdout/stderr catcher
#
from sys import argv
from subprocess import Popen, PIPE
import os, io
from threading import Thread
import Queue
def __main__():
    if (len(argv) > 1) and (argv[-1] == "-sub-"):
        import time, sys
        print "Application runned!"
        time.sleep(2)
        print "Slept 2 second"
        time.sleep(1)
        print "Slept 1 additional second",
        time.sleep(2)
        sys.stderr.write("Stderr output after 5 seconds")
        print "Eol on stdin"
        sys.stderr.write("Eol on stderr\n")
        time.sleep(1)
        print "Wow, we have end of work!",
    else:
        os.environ["PYTHONUNBUFFERED"]="1"
        try:
            p = Popen( argv + ["-sub-"],
                       bufsize=0, # line-buffered
                       stdin=PIPE, stdout=PIPE, stderr=PIPE )
        except WindowsError, W:
            if W.winerror==193:
                p = Popen( argv + ["-sub-"],
                           shell=True, # Try to run via shell
                           bufsize=0, # line-buffered
                           stdin=PIPE, stdout=PIPE, stderr=PIPE )
            else:
                raise
        inp = Queue.Queue()
        sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
        serr = io.open(p.stderr.fileno(), 'rb', closefd=False)
        def Pump(stream, category):
            queue = Queue.Queue()
            def rdr():
                while True:
                    buf = stream.read1(8192)
                    if len(buf)>0:
                        queue.put( buf )
                    else:
                        queue.put( None )
                        return
            def clct():
                active = True
                while active:
                    r = queue.get()
                    try:
                        while True:
                            r1 = queue.get(timeout=0.005)
                            if r1 is None:
                                active = False
                                break
                            else:
                                r += r1
                    except Queue.Empty:
                        pass
                    inp.put( (category, r) )
            for tgt in [rdr, clct]:
                th = Thread(target=tgt)
                th.setDaemon(True)
                th.start()
        Pump(sout, 'stdout')
        Pump(serr, 'stderr')

        while p.poll() is None:
            # App still working
            try:
                chan,line = inp.get(timeout = 1.0)
                if chan=='stdout':
                    print "STDOUT>>", line, "<?<"
                elif chan=='stderr':
                    print " ERROR==", line, "=?="
            except Queue.Empty:
                pass
        print "Finish"

if __name__ == '__main__':
    __main__()

Bir satırsonu ile bitmesi gerekmeyen şeyleri okumanıza izin veren birkaç cevaptan biri.
totaam

5

Bu sorunu bazı alt süreçleri okumak için ekliyorum. Stdout'u açın. İşte benim engelleme olmayan okuma çözümü:

import fcntl

def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.read()
    except:
        return ""

# Use example
from subprocess import *
sb = Popen("echo test && sleep 1000", shell=True, stdout=PIPE)
sb.kill()

# sb.stdout.read() # <-- This will block
non_block_read(sb.stdout)
'test\n'

5
Dokümanlara göre fcntl pencerelerde çalışmıyor .
n611x007

@anatolytechtonik kullanım msvcrt.kbhit()yerine
kedi

4

Engellenmeyen okuma bu sürümü değil özel modülleri gerektirmez ve out-of-the-box çalışacak Linux dağıtımlarının çoğunda üzerinde.

import os
import sys
import time
import fcntl
import subprocess

def async_read(fd):
    # set non-blocking flag while preserving old flags
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    # read char until EOF hit
    while True:
        try:
            ch = os.read(fd.fileno(), 1)
            # EOF
            if not ch: break                                                                                                                                                              
            sys.stdout.write(ch)
        except OSError:
            # waiting for data be available on fd
            pass

def shell(args, async=True):
    # merge stderr and stdout
    proc = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if async: async_read(proc.stdout)
    sout, serr = proc.communicate()
    return (sout, serr)

if __name__ == '__main__':
    cmd = 'ping 8.8.8.8'
    sout, serr = shell(cmd.split())

3

İşte konulara dayalı basit bir çözüm:

  • hem Linux hem de Windows üzerinde çalışır (güvenilmez select).
  • her ikisini de stdoutve stderreşzamansız olarak okur .
  • isteğe bağlı bekleme süresine sahip aktif yoklamaya güvenmez (CPU dostu).
  • kullanmaz asyncio(diğer kütüphanelerle çakışabilir).
  • alt süreç sona erene kadar çalışır.

printer.py

import time
import sys

sys.stdout.write("Hello\n")
sys.stdout.flush()
time.sleep(1)
sys.stdout.write("World!\n")
sys.stdout.flush()
time.sleep(1)
sys.stderr.write("That's an error\n")
sys.stderr.flush()
time.sleep(2)
sys.stdout.write("Actually, I'm fine\n")
sys.stdout.flush()
time.sleep(1)

reader.py

import queue
import subprocess
import sys
import threading


def enqueue_stream(stream, queue, type):
    for line in iter(stream.readline, b''):
        queue.put(str(type) + line.decode('utf-8'))
    stream.close()


def enqueue_process(process, queue):
    process.wait()
    queue.put('x')


p = subprocess.Popen('python printer.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
q = queue.Queue()
to = threading.Thread(target=enqueue_stream, args=(p.stdout, q, 1))
te = threading.Thread(target=enqueue_stream, args=(p.stderr, q, 2))
tp = threading.Thread(target=enqueue_process, args=(p, q))
te.start()
to.start()
tp.start()

while True:
    line = q.get()
    if line[0] == 'x':
        break
    if line[0] == '2':  # stderr
        sys.stdout.write("\033[0;31m")  # ANSI red color
    sys.stdout.write(line[1:])
    if line[0] == '2':
        sys.stdout.write("\033[0m")  # reset ANSI code
    sys.stdout.flush()

tp.join()
to.join()
te.join()

2

Windows ve Unix'te engellemeyen boruların ayarlanabilmesini sağladığı için bu yanıtı buraya eklemek.

Tüm ctypesdetaylar @ techtonik'in cevabı sayesinde .

Hem Unix hem de Windows sistemlerinde kullanılacak biraz değiştirilmiş bir sürüm var.

  • Python3 uyumlu (sadece küçük değişiklikler gerekli) .
  • Posix sürümünü içerir ve her ikisinde de kullanılacak özel durumu tanımlar.

Bu şekilde, Unix ve Windows kodu için aynı işlevi ve istisnayı kullanabilirsiniz.

# pipe_non_blocking.py (module)
"""
Example use:

    p = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            )

    pipe_non_blocking_set(p.stdout.fileno())

    try:
        data = os.read(p.stdout.fileno(), 1)
    except PortableBlockingIOError as ex:
        if not pipe_non_blocking_is_error_blocking(ex):
            raise ex
"""


__all__ = (
    "pipe_non_blocking_set",
    "pipe_non_blocking_is_error_blocking",
    "PortableBlockingIOError",
    )

import os


if os.name == "nt":
    def pipe_non_blocking_set(fd):
        # Constant could define globally but avoid polluting the name-space
        # thanks to: /programming/34504970
        import msvcrt

        from ctypes import windll, byref, wintypes, WinError, POINTER
        from ctypes.wintypes import HANDLE, DWORD, BOOL

        LPDWORD = POINTER(DWORD)

        PIPE_NOWAIT = wintypes.DWORD(0x00000001)

        def pipe_no_wait(pipefd):
            SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
            SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
            SetNamedPipeHandleState.restype = BOOL

            h = msvcrt.get_osfhandle(pipefd)

            res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
            if res == 0:
                print(WinError())
                return False
            return True

        return pipe_no_wait(fd)

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        from ctypes import GetLastError
        ERROR_NO_DATA = 232

        return (GetLastError() == ERROR_NO_DATA)

    PortableBlockingIOError = OSError
else:
    def pipe_non_blocking_set(fd):
        import fcntl
        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return True

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        return True

    PortableBlockingIOError = BlockingIOError

Eksik verilerin okunmasını önlemek için, kendi readline jeneratörümü yazdım (her satır için bayt dizesini döndürür).

Bu bir jeneratör, örneğin ...

def non_blocking_readlines(f, chunk=1024):
    """
    Iterate over lines, yielding b'' when nothings left
    or when new data is not yet available.

    stdout_iter = iter(non_blocking_readlines(process.stdout))

    line = next(stdout_iter)  # will be a line or b''.
    """
    import os

    from .pipe_non_blocking import (
            pipe_non_blocking_set,
            pipe_non_blocking_is_error_blocking,
            PortableBlockingIOError,
            )

    fd = f.fileno()
    pipe_non_blocking_set(fd)

    blocks = []

    while True:
        try:
            data = os.read(fd, chunk)
            if not data:
                # case were reading finishes with no trailing newline
                yield b''.join(blocks)
                blocks.clear()
        except PortableBlockingIOError as ex:
            if not pipe_non_blocking_is_error_blocking(ex):
                raise ex

            yield b''
            continue

        while True:
            n = data.find(b'\n')
            if n == -1:
                break

            yield b''.join(blocks) + data[:n + 1]
            data = data[n + 1:]
            blocks.clear()
        blocks.append(data)

(1) Bu yorum , Python 2'de readline()tıkanmasız borularla (set kullanımı gibi fcntl) çalışmadığını gösterir - artık doğru olmadığını düşünüyor musunuz? (cevabım fcntlaynı bilgileri sağlayan bağlantıyı ( ) içeriyor ancak şimdi silinmiş görünüyor). (2) Nasıl multiprocessing.connection.Pipekullanıldığını görünSetNamedPipeHandleState
jfs

Bunu sadece Python3'te test ettim. Ancak bu bilgileri de gördüm ve geçerliliğini koruyun. Ayrıca readline yerine kullanmak için kendi kodumu yazdım.
ideasman42

2

Orijinal sorucının sorunu var, ancak konuları çağırmak istemiyordu. Jesse'nin çözümünü borudan doğrudan okuma () ile karıştırdım ve satır okumaları için kendi arabellek işleyicim (ancak, alt işlemim - ping - her zaman tam satırlar <bir sistem sayfası boyutu yazdı). Sadece gobject-kayıtlı bir io saatinde okuyarak meşgul beklemekten kaçınırım. Bu gün genellikle konuları önlemek için bir gobject MainLoop içinde kod çalıştırın.

def set_up_ping(ip, w):
# run the sub-process
# watch the resultant pipe
p = subprocess.Popen(['/bin/ping', ip], stdout=subprocess.PIPE)
# make stdout a non-blocking file
fl = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
stdout_gid = gobject.io_add_watch(p.stdout, gobject.IO_IN, w)
return stdout_gid # for shutting down

İzleyici

def watch(f, *other):
print 'reading',f.read()
return True

Ve ana program bir ping kurar ve sonra gobject mail loop'u çağırır.

def main():
set_up_ping('192.168.1.8', watch)
# discard gid as unused here
gobject.MainLoop().run()

Gobject'teki geri çağrılara başka herhangi bir çalışma eklenir.


2

Modern Python'da işler çok daha iyi.

İşte basit bir çocuk programı "hello.py":

#!/usr/bin/env python3

while True:
    i = input()
    if i == "quit":
        break
    print(f"hello {i}")

Ve onunla etkileşim kurmak için bir program:

import asyncio


async def main():
    proc = await asyncio.subprocess.create_subprocess_exec(
        "./hello.py", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE
    )
    proc.stdin.write(b"bob\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"alice\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"quit\n")
    await proc.wait()


asyncio.run(main())

Bu çıktı:

b'hello bob\n'
b'hello alice\n'

Hem burada hem de ilgili sorularda önceki yanıtların neredeyse hepsinin de olduğu gerçek modelin, çocuğun stdout dosyası tanımlayıcısını engellemeyecek şekilde ayarlamak ve daha sonra bir tür seçme döngüsünde yoklamak olduğunu unutmayın. Bu günler, elbette, bu döngü asyncio tarafından sağlanır.


1

Seçme modülü sonraki kullanışlı girdi nerede olduğunu belirlemeye yardımcı olur.

Ancak, ayrı iş parçacıklarıyla neredeyse her zaman daha mutlu olursunuz. Biri stdin'i okurken engelleme yapar, diğeri nerede engellenmesini istemezse yapar.


11
Bu cevabın iki nedenden dolayı yararsız olduğunu düşünüyorum: (a) Seçme modülü, OP'nin taşınabilir bir çözüme sahip olma niyetini yenen Windows altındaki borularda (sağlanan bağlantı açıkça belirtildiği gibi) çalışmayacaktır. (b) Zaman uyumsuz iş parçacıkları, üst ve alt süreç arasında eşzamanlı bir diyaloga izin vermez. Üst süreç, alt öğeden okunan bir sonraki satıra göre bir sonraki eylemi göndermek isterse ne olur ?!
ThomasH

4
select, standart C anlambilimine sahip olmadığı ve kısmi veri döndürmeyeceği için Python'un okumalarının seçimden sonra bile engelleneceği için de yararlı değildir.
Helmut Grohne

Çocuğun çıktısından okumak için ayrı bir test, buna benzer sorunumu çözdü. Eşzamanlı etkileşime ihtiyacınız varsa, sanırım bu çözümü kullanamazsınız (hangi çıktıyı bekleyeceğinizi bilmiyorsanız). Bu cevabı kabul ederdim
Emiliano

1

neden iplik ve kuyruk rahatsız? readline () 'dan farklı olarak, BufferedReader.read1 () blok \ r \ n için beklemeyecek, herhangi bir çıktı geliyorsa ASAP döndürür.

#!/usr/bin/python
from subprocess import Popen, PIPE, STDOUT
import io

def __main__():
    try:
        p = Popen( ["ping", "-n", "3", "127.0.0.1"], stdin=PIPE, stdout=PIPE, stderr=STDOUT )
    except: print("Popen failed"); quit()
    sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
    while True:
        buf = sout.read1(1024)
        if len(buf) == 0: break
        print buf,

if __name__ == '__main__':
    __main__()

Gelen bir şey yoksa ASAP'a geri dönecek mi? Değilse engelliyor.
Mathieu Pagé

@ MathieuPagé haklı. read1boru hala açık ancak giriş yokken gerçekleşen ilk temel okuma blokları bloke olursa.
Jack O'Connor

1

Benim durumumda, arka plan uygulamalarından çıktıyı yakalayan ve artıran bir zamanlama modülüne ihtiyacım vardı (zaman damgaları, renkler vb. Ekleyerek).

Ben gerçek I / O yapan bir arka plan iş parçacığı ile sona erdi. Aşağıdaki kod yalnızca POSIX platformları içindir. Gerekli olmayan parçaları çıkardım.

Birisi bu canavarı uzun vadede kullanacaksa, açık tanımlayıcıları yönetmeyi düşünün. Benim durumumda bu büyük bir sorun değildi.

# -*- python -*-
import fcntl
import threading
import sys, os, errno
import subprocess

class Logger(threading.Thread):
    def __init__(self, *modules):
        threading.Thread.__init__(self)
        try:
            from select import epoll, EPOLLIN
            self.__poll = epoll()
            self.__evt = EPOLLIN
            self.__to = -1
        except:
            from select import poll, POLLIN
            print 'epoll is not available'
            self.__poll = poll()
            self.__evt = POLLIN
            self.__to = 100
        self.__fds = {}
        self.daemon = True
        self.start()

    def run(self):
        while True:
            events = self.__poll.poll(self.__to)
            for fd, ev in events:
                if (ev&self.__evt) != self.__evt:
                    continue
                try:
                    self.__fds[fd].run()
                except Exception, e:
                    print e

    def add(self, fd, log):
        assert not self.__fds.has_key(fd)
        self.__fds[fd] = log
        self.__poll.register(fd, self.__evt)

class log:
    logger = Logger()

    def __init__(self, name):
        self.__name = name
        self.__piped = False

    def fileno(self):
        if self.__piped:
            return self.write
        self.read, self.write = os.pipe()
        fl = fcntl.fcntl(self.read, fcntl.F_GETFL)
        fcntl.fcntl(self.read, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        self.fdRead = os.fdopen(self.read)
        self.logger.add(self.read, self)
        self.__piped = True
        return self.write

    def __run(self, line):
        self.chat(line, nl=False)

    def run(self):
        while True:
            try: line = self.fdRead.readline()
            except IOError, exc:
                if exc.errno == errno.EAGAIN:
                    return
                raise
            self.__run(line)

    def chat(self, line, nl=True):
        if nl: nl = '\n'
        else: nl = ''
        sys.stdout.write('[%s] %s%s' % (self.__name, line, nl))

def system(command, param=[], cwd=None, env=None, input=None, output=None):
    args = [command] + param
    p = subprocess.Popen(args, cwd=cwd, stdout=output, stderr=output, stdin=input, env=env, bufsize=0)
    p.wait()

ls = log('ls')
ls.chat('go')
system("ls", ['-l', '/'], output=ls)

date = log('date')
date.chat('go')
system("date", output=date)

1

Benim sorunum biraz farklı bir çalışma sürecinden hem stdout hem de stderr toplamak istedim, ama sonuçta üretilen bir widget olarak çıktı oluşturmak istedim çünkü aynı.

Başka bir komut dosyası çalıştırmak ve çıktısını toplamak gibi ortak bir görevi gerçekleştirmek için gerekli olmamalıdır gibi kuyrukları veya ek iş parçacıkları kullanarak önerilen geçici çözümlerin çoğuna başvurmak istemiyordu.

Önerilen çözümleri ve python belgelerini okuduktan sonra sorunumu aşağıdaki uygulama ile çözdüm. Evet, selectişlev çağrısını kullandığım için sadece POSIX için çalışıyor .

Belgelerin kafa karıştırıcı olduğunu ve uygulamanın yaygın bir betik görevi için garip olduğunu kabul ediyorum. Python'un eski sürümlerinin farklı varsayılanları Popenve farklı açıklamaları olduğuna inanıyorum, bu yüzden çok fazla karışıklık yarattı. Bu hem Python 2.7.12 hem de 3.5.2 için iyi çalışıyor gibi görünüyor.

Anahtar, bufsize=1satır arabelleğe almayı ayarlamak ve sonra universal_newlines=Trueayar sırasında varsayılan gibi görünen ikili yerine bir metin dosyası olarak işlemekti bufsize=1.

class workerThread(QThread):
   def __init__(self, cmd):
      QThread.__init__(self)
      self.cmd = cmd
      self.result = None           ## return code
      self.error = None            ## flag indicates an error
      self.errorstr = ""           ## info message about the error

   def __del__(self):
      self.wait()
      DEBUG("Thread removed")

   def run(self):
      cmd_list = self.cmd.split(" ")   
      try:
         cmd = subprocess.Popen(cmd_list, bufsize=1, stdin=None
                                        , universal_newlines=True
                                        , stderr=subprocess.PIPE
                                        , stdout=subprocess.PIPE)
      except OSError:
         self.error = 1
         self.errorstr = "Failed to execute " + self.cmd
         ERROR(self.errorstr)
      finally:
         VERBOSE("task started...")
      import select
      while True:
         try:
            r,w,x = select.select([cmd.stdout, cmd.stderr],[],[])
            if cmd.stderr in r:
               line = cmd.stderr.readline()
               if line != "":
                  line = line.strip()
                  self.emit(SIGNAL("update_error(QString)"), line)
            if cmd.stdout in r:
               line = cmd.stdout.readline()
               if line == "":
                  break
               line = line.strip()
               self.emit(SIGNAL("update_output(QString)"), line)
         except IOError:
            pass
      cmd.wait()
      self.result = cmd.returncode
      if self.result < 0:
         self.error = 1
         self.errorstr = "Task terminated by signal " + str(self.result)
         ERROR(self.errorstr)
         return
      if self.result:
         self.error = 1
         self.errorstr = "exit code " + str(self.result)
         ERROR(self.errorstr)
         return
      return

HATA, HATA AYIKLAMA ve VERBOSE, terminale çıktı yazdıran makrolardır.

Bu çözüm, hala engelleme readlineişlevini kullandığı için% 99,99 IMHO etkindir , bu nedenle alt sürecin güzel olduğunu ve tüm satırları çıkardığını varsayıyoruz.

Python'da hala yeni olduğum için çözümü geliştirmek için geri bildirim almaktan memnuniyet duyuyorum.


Bu özel durumda, Popen yapıcısında stderr = subprocess.STDOUT öğesini ayarlayabilir ve cmd.stdout.readline () öğesinden tüm çıktıları alabilirsiniz.
Aaron

Güzel açık bir örnek. Select.select () ile ilgili sorun yaşıyordum ama bu benim için çözdü.
maharvey67


0

JF Sebastian'ın cevabından ve diğer birkaç kaynaktan çalışarak, basit bir alt süreç yöneticisini bir araya getirdim. Engellemesiz okuma isteğinin yanı sıra birkaç işlemi paralel olarak çalıştırır. Herhangi bir işletim sistemine özgü çağrı (farkında olduğum) kullanmaz ve bu nedenle herhangi bir yerde çalışması gerekir.

Pypi'den temin edilebilir, bu yüzden sadece pip install shelljob. Örnekler ve tam dokümanlar için proje sayfasına bakın .


0

EDIT: Bu uygulama hala engelliyor. Bunun yerine JFSebastian'ın yanıtını kullanın.

En iyi yanıtı denedim , ancak iş parçacığı kodunun ek risk ve bakımı endişe vericiydi.

İo modülüne bakarak (ve 2.6 ile sınırlı), Ben BufferedReader'ı buldum. Bu benim ipliksiz, engellemeyen çözümüm.

import io
from subprocess import PIPE, Popen

p = Popen(['myprogram.exe'], stdout=PIPE)

SLEEP_DELAY = 0.001

# Create an io.BufferedReader on the file descriptor for stdout
with io.open(p.stdout.fileno(), 'rb', closefd=False) as buffer:
  while p.poll() == None:
      time.sleep(SLEEP_DELAY)
      while '\n' in bufferedStdout.peek(bufferedStdout.buffer_size):
          line = buffer.readline()
          # do stuff with the line

  # Handle any remaining output after the process has ended
  while buffer.peek():
    line = buffer.readline()
    # do stuff with the line

denedin for line in iter(p.stdout.readline, ""): # do stuff with the linemi Dişsiz (tek iş parçacığı) ve kodunuz engellendiğinde engeller.
jfs

@ jf-sebastian Evet, sonunda cevabına döndüm. Uygulamam ara sıra engelleniyor. Cevabımı, diğerlerini bu rotadan aşağı gitmemeleri konusunda uyarmak için düzenleyeceğim.
romc

0

Son zamanlarda aynı sorun üzerinde tökezledi Ben bloke olmayan modda akışı (alt süreçte kuyruk) zaman bir satır okumak gerekir Sonraki sorunları önlemek istedim: cpu yakmak için değil, bir bayt tarafından akışı okumak yok ( readline gibi), vb.

İşte benim uygulama https://gist.github.com/grubberr/5501e1a9760c3eab5e0a pencereleri desteklemiyor (anket), EOF ile baş etmiyor, ama benim için iyi çalışıyor


İplik bazlı cevap vermez değil cpu yakmak (eğer keyfi belirtebilirsiniz timeoutçözümünüzdeki gibi) ve .readline()okur daha bir defada birden byte ( bufsize=1vasıta hat yazmak için sadece ilgili (-buffered)). Başka hangi sorunları buldunuz? Yalnızca bağlantı yanıtları çok yararlı değildir.
jfs

0

Bu, alt işlemde etkileşimli komutu çalıştırmak için bir örnektir ve stdout, sözde terminal kullanılarak etkileşimlidir. Şuraya başvurabilirsiniz: https://stackoverflow.com/a/43012138/3555925

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import select
import termios
import tty
import pty
from subprocess import Popen

command = 'bash'
# command = 'docker run -it --rm centos /bin/bash'.split()

# save original tty setting then set it to raw mode
old_tty = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())

# open pseudo-terminal to interact with subprocess
master_fd, slave_fd = pty.openpty()

# use os.setsid() make it run in a new process group, or bash job control will not be enabled
p = Popen(command,
          preexec_fn=os.setsid,
          stdin=slave_fd,
          stdout=slave_fd,
          stderr=slave_fd,
          universal_newlines=True)

while p.poll() is None:
    r, w, e = select.select([sys.stdin, master_fd], [], [])
    if sys.stdin in r:
        d = os.read(sys.stdin.fileno(), 10240)
        os.write(master_fd, d)
    elif master_fd in r:
        o = os.read(master_fd, 10240)
        if o:
            os.write(sys.stdout.fileno(), o)

# restore tty settings back
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)

0

Bu çözüm select, bir G / Ç akışından "kullanılabilir verileri okumak" için modülü kullanır . Bu işlev başlangıçta veri bulunana kadar engeller, ancak yalnızca kullanılabilir olan verileri okur ve daha fazla engellemez.

selectModülü kullandığı göz önüne alındığında , bu sadece Unix üzerinde çalışır.

Kod tamamen PEP8 uyumludur.

import select


def read_available(input_stream, max_bytes=None):
    """
    Blocks until any data is available, then all available data is then read and returned.
    This function returns an empty string when end of stream is reached.

    Args:
        input_stream: The stream to read from.
        max_bytes (int|None): The maximum number of bytes to read. This function may return fewer bytes than this.

    Returns:
        str
    """
    # Prepare local variables
    input_streams = [input_stream]
    empty_list = []
    read_buffer = ""

    # Initially block for input using 'select'
    if len(select.select(input_streams, empty_list, empty_list)[0]) > 0:

        # Poll read-readiness using 'select'
        def select_func():
            return len(select.select(input_streams, empty_list, empty_list, 0)[0]) > 0

        # Create while function based on parameters
        if max_bytes is not None:
            def while_func():
                return (len(read_buffer) < max_bytes) and select_func()
        else:
            while_func = select_func

        while True:
            # Read single byte at a time
            read_data = input_stream.read(1)
            if len(read_data) == 0:
                # End of stream
                break
            # Append byte to string buffer
            read_buffer += read_data
            # Check if more data is available
            if not while_func():
                break

    # Return read buffer
    return read_buffer

0

Ayrıca Jesse tarafından açıklanan sorunla karşı karşıya kaldım ve Bradley , Andy ve diğerleri gibi "select" kullanarak çözdüm ama yoğun bir döngü önlemek için bir engelleme modunda. Sahte stdin olarak bir kukla Boru kullanır. Seçim blokları ve stdin veya borunun hazır olmasını bekleyin. Bir tuşa basıldığında stdin seçimi kaldırır ve anahtar değeri read (1) ile alınabilir. Farklı bir iplik boruya yazdığında, boru seçimin engelini kaldırır ve stdin ihtiyacının sona erdiğinin bir göstergesi olarak alınabilir. İşte bazı referans kodu:

import sys
import os
from select import select

# -------------------------------------------------------------------------    
# Set the pipe (fake stdin) to simulate a final key stroke
# which will unblock the select statement
readEnd, writeEnd = os.pipe()
readFile = os.fdopen(readEnd)
writeFile = os.fdopen(writeEnd, "w")

# -------------------------------------------------------------------------
def getKey():

    # Wait for stdin or pipe (fake stdin) to be ready
    dr,dw,de = select([sys.__stdin__, readFile], [], [])

    # If stdin is the one ready then read it and return value
    if sys.__stdin__ in dr:
        return sys.__stdin__.read(1)   # For Windows use ----> getch() from module msvcrt

    # Must finish
    else:
        return None

# -------------------------------------------------------------------------
def breakStdinRead():
    writeFile.write(' ')
    writeFile.flush()

# -------------------------------------------------------------------------
# MAIN CODE

# Get key stroke
key = getKey()

# Keyboard input
if key:
    # ... do your stuff with the key value

# Faked keystroke
else:
    # ... use of stdin finished

# -------------------------------------------------------------------------
# OTHER THREAD CODE

breakStdinRead()

NOT: Bu işlemin Windows'da yapılabilmesi için borunun bir soketle değiştirilmesi gerekir. Henüz denemedim ama belgelere göre çalışması gerekir.
gonzaedu61

0

Deneyin wexpect alternatif pencereler olan pexpect .

import wexpect

p = wexpect.spawn('myprogram.exe')
p.stdout.readline('.')               // regex pattern of any character
output_str = p.after()

0

Unix benzeri sistemlerde ve Python 3.5+ os.set_blocking'de tam olarak söylediklerini yapar.

import os
import time
import subprocess

cmd = 'python3', '-c', 'import time; [(print(i), time.sleep(1)) for i in range(5)]'
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
os.set_blocking(p.stdout.fileno(), False)
start = time.time()
while True:
    # first iteration always produces empty byte string in non-blocking mode
    for i in range(2):    
        line = p.stdout.readline()
        print(i, line)
        time.sleep(0.5)
    if time.time() > start + 5:
        break
p.terminate()

Bu çıktılar:

1 b''
2 b'0\n'
1 b''
2 b'1\n'
1 b''
2 b'2\n'
1 b''
2 b'3\n'
1 b''
2 b'4\n'

Yorum ile os.set_blocking:

0 b'0\n'
1 b'1\n'
0 b'2\n'
1 b'3\n'
0 b'4\n'
1 b''

-2

İşte python'da engellemeyen okumaları ve arka plan yazmalarını destekleyen bir modül:

https://pypi.python.org/pypi/python-nonblock

Bir işlev sağlar,

mümkünse akıştan veri okuyacak nonblock_read, aksi takdirde boş bir dize döndürür (veya akış diğer tarafta kapatılırsa ve olası tüm veriler okunduysa Yok)

Ayrıca python-subprocess2 modülünü,

https://pypi.python.org/pypi/python-subprocess2

alt işlem modülüne eklenir. Bu nedenle, "subprocess.Popen" öğesinden döndürülen nesneye runInBackground ek bir yöntem eklenir. Bu, bir iş parçacığını başlatır ve ana iş parçacığınızı engellemeden stdout / stderr öğesine bir şeyler yazılırken otomatik olarak doldurulacak bir nesneyi döndürür.

Zevk almak!


Bu engelleme dışı modülü denemek istiyorum , ancak bazı Linux prosedürlerinde nispeten yeniyim . Bu rutinleri tam olarak nasıl kurabilirim? Raspberry Pi için Debian Linux'un bir çeşidi olan Raspbian Jessie'yi kullanıyorum. Ben 'sudo apt-get install nonblock' ve python-nonblock denedim ve her ikisi de bir hata attı - bulunamadı. Zip dosyasını bu siteden indirdim pypi.python.org/pypi/python-nonblock , ama onunla ne yapacağımı bilmiyorum. Teşekkürler .... RDK
RDK
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.