195 votes

Comment capturer la sortie stdout d'un appel de fonction Python ?

J'utilise une librairie Python qui fait quelque chose à un objet

do_something(my_object)

et le modifie. Ce faisant, il imprime certaines statistiques sur stdout, et j'aimerais avoir un aperçu de ces informations. La bonne solution serait de modifier do_something() pour renvoyer les informations pertinentes,

out = do_something(my_object)

mais il faudra attendre un certain temps avant que les développeurs de do_something() à ce sujet. Comme solution de contournement, j'ai pensé à analyser n'importe quel fichier do_something() écrit sur stdout.

Comment puis-je capturer la sortie stdout entre deux points du code, par exemple,

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

?

270voto

kindall Points 60645

Essayez ce gestionnaire de contexte :

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

Utilisation :

with Capturing() as output:
    do_something(my_object)

output est maintenant une liste contenant les lignes imprimées par l'appel de la fonction.

Utilisation avancée :

Ce qui n'est peut-être pas évident, c'est que cette opération peut être effectuée plusieurs fois et que les résultats sont concaténés :

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)

Sortie :

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

Mise à jour : Ils ont ajouté redirect_stdout() à contextlib en Python 3.4 (ainsi que redirect_stderr() ). Vous pouvez donc utiliser io.StringIO avec cela pour obtenir un résultat similaire (bien que Capturing être une liste ainsi qu'un gestionnaire de contexte est sans doute plus pratique).

197voto

ForeverWintr Points 611

Dans python >= 3.4, contextlib contient une fonction redirect_stdout décorateur. Il peut être utilisé pour répondre à votre question de la manière suivante :

import io
from contextlib import redirect_stdout

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

De les docs :

Gestionnaire de contexte pour rediriger temporairement sys.stdout vers un autre fichier ou un objet de type fichier.

Cet outil ajoute de la flexibilité aux fonctions ou classes existantes dont sortie est câblée vers stdout.

Par exemple, la sortie de help() est normalement envoyée à sys.stdout. Vous pouvez pouvez capturer cette sortie dans une chaîne en la redirigeant vers un objet io.StringIO :

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

Pour envoyer la sortie de help() vers un fichier sur le disque, redirigez la sortie vers un fichier normal :

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

Pour envoyer la sortie de help() à sys.stderr :

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

Notez que l'effet secondaire global sur sys.stdout signifie que ce gestionnaire de contexte n'est pas sys. contexte n'est pas adapté à une utilisation dans du code de bibliothèque et la plupart des applications. Il n'a pas non plus d'effet sur la sortie des sous-processus. Cependant, c'est toujours une approche utile pour de nombreux scripts utilitaires.

Ce gestionnaire de contexte est réentrant.

5voto

miXo Points 62

Voici une solution asynchrone utilisant les file pipes.

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

Exemple d'utilisation :

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()

3voto

Hunger Points 326

Sur la base de kindall y ForeverWintr La réponse de la Commission.

Je crée redirect_stdout fonction pour Python<3.4 :

import io
from contextlib import contextmanager

@contextmanager
def redirect_stdout(f):
    try:
        _stdout = sys.stdout
        sys.stdout = f
        yield
    finally:
        sys.stdout = _stdout

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

3voto

Cliff Kerr Points 211

En s'inspirant également des réponses de @kindall et @ForeveWintr, voici une classe qui accomplit ceci. La principale différence par rapport aux réponses précédentes est que cette classe capture les éléments suivants comme une chaîne et non comme un StringIO ce qui est beaucoup plus pratique pour travailler avec !

import io
from collections import UserString
from contextlib import redirect_stdout

class capture(UserString, str, redirect_stdout):
    '''
    Captures stdout (e.g., from ``print()``) as a variable.

    Based on ``contextlib.redirect_stdout``, but saves the user the trouble of
    defining and reading from an IO stream. Useful for testing the output of functions
    that are supposed to print certain output.
    '''

    def __init__(self, seq='', *args, **kwargs):
        self._io = io.StringIO()
        UserString.__init__(self, seq=seq, *args, **kwargs)
        redirect_stdout.__init__(self, self._io)
        return

    def __enter__(self, *args, **kwargs):
        redirect_stdout.__enter__(self, *args, **kwargs)
        return self

    def __exit__(self, *args, **kwargs):
        self.data += self._io.getvalue()
        redirect_stdout.__exit__(self, *args, **kwargs)
        return

    def start(self):
        self.__enter__()
        return self

    def stop(self):
        self.__exit__(None, None, None)
        return

Exemples :

# Using with...as
with capture() as txt1:
    print('Assign these lines')
    print('to a variable')

# Using start()...stop()
txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()

print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)

Ceci est mis en œuvre dans Sciris como sc.capture() .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X