Bir Python işlev çağrısından standart çıktı nasıl yakalanır?


112

Bir nesneye bir şeyler yapan bir Python kitaplığı kullanıyorum

do_something(my_object)

ve onu değiştirir. Bunu yaparken, stdout'a bazı istatistikler yazdırıyor ve bu bilgiyi kavramak istiyorum. Uygun çözüm do_something(), ilgili bilgileri döndürmek için değişiklik yapmak olacaktır ,

out = do_something(my_object)

ancak geliştiricilerin do_something()bu konuya ulaşması biraz zaman alacak. Geçici bir çözüm olarak, do_something()stdout'a ne yazıyorsa onu ayrıştırmayı düşündüm .

Koddaki iki nokta arasındaki standart çıktıyı nasıl yakalayabilirim, örneğin,

start_capturing()
do_something(my_object)
out = end_capturing()

?



Bağlantılı sorudaki cevabım burada da geçerlidir.
Martijn Pieters

Bunu bir kez yapmaya çalıştım ve bulduğum en iyi cevap şuydu: stackoverflow.com/a/3113913/1330293
elyase

@elyase bağlantılı yanıt zarif bir çözüm
Pykler

Bu cevaba bakın .
martineau

Yanıtlar:


185

Bu bağlam yöneticisini deneyin:

from io import StringIO 
import sys

class Capturing(list):
    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._stringio = StringIO()
        return self
    def __exit__(self, *args):
        self.extend(self._stringio.getvalue().splitlines())
        del self._stringio    # free up some memory
        sys.stdout = self._stdout

Kullanım:

with Capturing() as output:
    do_something(my_object)

output artık işlev çağrısı tarafından yazdırılan satırları içeren bir listedir.

Gelişmiş kullanım:

Açık olmayan şey, bunun birden fazla kez yapılabileceği ve sonuçların birleştirilebileceğidir:

with Capturing() as output:
    print('hello world')

print('displays on screen')

with Capturing(output) as output:  # note the constructor argument
    print('hello world2')

print('done')
print('output:', output)

Çıktı:

displays on screen                     
done                                   
output: ['hello world', 'hello world2']

Güncelleme : Onlar ekledi redirect_stdout()için contextlib(birlikte Python 3.4'te redirect_stderr()). Böylece io.StringIObenzer bir sonuç elde etmek için bununla kullanabilirsiniz (yine Capturingde bir liste ve bağlam yöneticisi olmak muhtemelen daha uygundur).


Teşekkürler! Ve gelişmiş bölümü eklediğiniz için teşekkürler ... Başlangıçta yakalanan metni listeye yapıştırmak için bir dilim ataması kullandım, sonra kendimi kafama taktım ve .extend()bunun yerine, tıpkı sizin de fark ettiğiniz gibi sıralı olarak kullanılabilmesi için kullandım. :-)
kindall

Not: Tekrar tekrar kullanılacaksa , üye tarafından tutulan hafızanın bir kısmını serbest bırakmak için yönteme çağrıdan self._stringio.truncate(0)sonra bir eklemenizi öneririm . self.extend()__exit__()_stringio
martineau

25
Harika cevap, teşekkürler. Python 3 için, from io import StringIObağlam yöneticisinde ilk satır yerine kullanın .
Wtower

1
Bu iş parçacığı güvenli mi? Do_something çalışırken başka bir evre / çağrı print () kullanırsa ne olur?
Derorrist

1
Bu cevap, paylaşılan C kütüphanelerinden çıktı için çalışmayacaktır, onun yerine bu cevaba bakınız .
craymichael

82

Python> = 3.4'te, contextlib bir redirect_stdoutdekoratör içerir . Sorunuzu şu şekilde yanıtlamak için kullanılabilir:

import io
from contextlib import redirect_stdout

f = io.StringIO()
with redirect_stdout(f):
    do_something(my_object)
out = f.getvalue()

Gönderen docs :

Sys.stdout'u başka bir dosyaya veya dosya benzeri bir nesneye geçici olarak yeniden yönlendirmek için bağlam yöneticisi.

Bu araç, çıktıları standart çıktıya bağlı olan mevcut işlevlere veya sınıflara esneklik katar.

Örneğin, help () çıktısı normalde sys.stdout'a gönderilir. Çıkışı bir io.StringIO nesnesine yeniden yönlendirerek bu çıktıyı bir dizede yakalayabilirsiniz:

  f = io.StringIO() 
  with redirect_stdout(f):
      help(pow) 
  s = f.getvalue()

Yardımın () çıktısını diskteki bir dosyaya göndermek için, çıktıyı normal bir dosyaya yeniden yönlendirin:

 with open('help.txt', 'w') as f:
     with redirect_stdout(f):
         help(pow)

Help () çıktısını sys.stderr'e göndermek için:

with redirect_stdout(sys.stderr):
    help(pow)

Sys.stdout üzerindeki genel yan etkinin, bu bağlam yöneticisinin kitaplık kodunda ve çoğu iş parçacığı uygulamasında kullanım için uygun olmadığı anlamına geldiğini unutmayın. Alt süreçlerin çıktısı üzerinde de etkisi yoktur. Bununla birlikte, birçok yardımcı program betikleri için hala yararlı bir yaklaşımdır.

Bu bağlam yöneticisi evreseldir.


denendiğinde f = io.StringIO() with redirect_stdout(f): logger = getLogger('test_logger') logger.debug('Test debug message') out = f.getvalue() self.assertEqual(out, 'DEBUG:test_logger:Test debug message'). Bana bir hata veriyor:AssertionError: '' != 'Test debug message'
Eziz Durdyyev

Bu, yanlış bir şey yaptığım veya standart çıkış günlüğünü yakalayamadığım anlamına geliyor.
Eziz Durdyyev

@EzizDurdyyev, logger.debugvarsayılan olarak stdout'a yazmaz. Günlük aramanızı değiştirirseniz print()mesajı görmeniz gerekir.
ForeverWintr

Evet, biliyorum, ama ben biliyorum o kadar gibi Stdout'a yazma olun: stream_handler = logging.StreamHandler(sys.stdout). Ve o işleyiciyi kaydedicime ekle. yani stdout'a yazmalı ve redirect_stdoutonu yakalamalı, değil mi?
Eziz Durdyyev

Sorunun, kaydedicinizi yapılandırma şeklinizden kaynaklandığından şüpheleniyorum. Redirect_stdout olmadan stdout'a yazdırdığını doğrulardım. Varsa, bağlam yöneticisi çıkana kadar arabellek boşaltılmıyor olabilir.
ForeverWintr

0

İşte dosya kanallarını kullanan bir zaman uyumsuz çözüm.

import threading
import sys
import os

class Capturing():
    def __init__(self):
        self._stdout = None
        self._stderr = None
        self._r = None
        self._w = None
        self._thread = None
        self._on_readline_cb = None

    def _handler(self):
        while not self._w.closed:
            try:
                while True:
                    line = self._r.readline()
                    if len(line) == 0: break
                    if self._on_readline_cb: self._on_readline_cb(line)
            except:
                break

    def print(self, s, end=""):
        print(s, file=self._stdout, end=end)

    def on_readline(self, callback):
        self._on_readline_cb = callback

    def start(self):
        self._stdout = sys.stdout
        self._stderr = sys.stderr
        r, w = os.pipe()
        r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
        self._r = r
        self._w = w
        sys.stdout = self._w
        sys.stderr = self._w
        self._thread = threading.Thread(target=self._handler)
        self._thread.start()

    def stop(self):
        self._w.close()
        if self._thread: self._thread.join()
        self._r.close()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

Örnek kullanım:

from Capturing import *
import time

capturing = Capturing()

def on_read(line):
    # do something with the line
    capturing.print("got line: "+line)

capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()
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.