J'essayais d'obtenir le contenu de sys.stdout dans une chaîne. J'ai essayé l'évidence:

def get_stdout():
    import sys

    print('a')
    print('b')
    print('c')

    repr(sys.stdout)

    contents = ""
    #with open('some_file.txt','r') as f:
    #with open(sys.stdout) as f:
    for line in sys.stdout.readlines():
        contents += line
    print(contents)

Mais cela donne l'erreur:

Exception has occurred: UnsupportedOperation
not readable

Alors, comment puis-je simplement modifier les autorisations de ce fichier déjà ouvert?

J'ai essayé:

    sys.stdout.mode = 'r'

Mais cela donne toujours la même erreur ...

D'autres choses qui fonctionneraient seraient de simplement me donner le nom / chemin de stdout d'une manière indépendante du matériel.


Une autre chose qui fonctionnerait serait de me permettre de mettre le contenu de sys.stdout après avoir exécuté mon script principal dans une chaîne.


J'ai lu des questions qui n'ont pas aidé:

2
Pinocchio 7 avril 2020 à 19:35

4 réponses

Meilleure réponse

Ma réponse précédente à cette question n'est pas aussi bonne que je le pensais ( https://stackoverflow.com/a/61087617/3167448). Je pense que la vraie réponse à cette question est d'utiliser simplement un enregistreur. Je ne savais pas ce qu'étaient les bûcherons jusqu'à récemment, mais ils sont bien meilleurs.

Il est préférable de créer un objet logger qui envoie vos chaînes dans un fichier journal ET vers la sortie standard. Il vous permet même de router plus finement les messages en fonction du niveau de seuil. Voici le code:

def logger_SO_print_and_write_to_my_stdout():
    """My sample logger code to print to screen and write to file (the same thing).

    Note: trying to replace this old answer of mine using a logger: 
    - https://github.com/CoreyMSchafer/code_snippets/tree/master/Logging-Advanced

    Credit: 
    - https://www.youtube.com/watch?v=jxmzY9soFXg&t=468s
    - https://github.com/CoreyMSchafer/code_snippets/tree/master/Logging-Advanced
    - https://stackoverflow.com/questions/21494468/about-notset-in-python-logging/21494716#21494716

    Other resources:
    - https://docs.python-guide.org/writing/logging/
    - https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
    """
    from pathlib import Path
    import logging
    import os
    import sys
    from datetime import datetime

    ## create directory (& its parents) if it does not exist otherwise do nothing :)
    # get current time
    current_time = datetime.now().strftime('%b%d_%H-%M-%S') 
    logs_dirpath = Path(f'~/logs/python_playground_logs_{current_time}/').expanduser()
    logs_dirpath.mkdir(parents=True, exist_ok=True)
    my_stdout_filename = logs_dirpath / Path('my_stdout.log')
    # remove my_stdout if it exists (note you can also just create a new log dir/file each time or append to the end of the log file your using)
    #os.remove(my_stdout_filename) if os.path.isfile(my_stdout_filename) else None

    ## create top logger
    logger = logging.getLogger(__name__) # loggers are created in hierarchy using dot notation, thus __name__ ensures no name collisions.
    logger.setLevel(logging.DEBUG) # note: use logging.DEBUG, CAREFUL with logging.UNSET: https://stackoverflow.com/questions/21494468/about-notset-in-python-logging/21494716#21494716

    ## log to my_stdout.log file
    file_handler = logging.FileHandler(filename=my_stdout_filename)
    #file_handler.setLevel(logging.INFO) # not setting it means it inherits the logger. It will log everything from DEBUG upwards in severity to this handler.
    log_format = "{asctime}:{levelname}:{lineno}:{name}:{message}" # see for logrecord attributes https://docs.python.org/3/library/logging.html#logrecord-attributes
    formatter = logging.Formatter(fmt=log_format, style='{') # set the logging format at for this handler
    file_handler.setFormatter(fmt=formatter)

    ## log to stdout/screen
    stdout_stream_handler = logging.StreamHandler(stream=sys.stdout) # default stderr, though not sure the advatages of logging to one or the other
    #stdout_stream_handler.setLevel(logging.INFO) # Note: having different set levels means that we can route using a threshold what gets logged to this handler
    log_format = "{name}:{levelname}:-> {message}" # see for logrecord attributes https://docs.python.org/3/library/logging.html#logrecord-attributes
    formatter = logging.Formatter(fmt=log_format, style='{') # set the logging format at for this handler
    stdout_stream_handler.setFormatter(fmt=formatter)

    logger.addHandler(hdlr=file_handler) # add this file handler to top logger
    logger.addHandler(hdlr=stdout_stream_handler) # add this file handler to top logger

    logger.log(logging.NOTSET, 'notset')
    logger.debug('debug')
    logger.info('info')
    logger.warning('warning')
    logger.error('error')
    logger.critical('critical')

Contenu du journal:

2020-04-16 11:28:24,987:DEBUG:154:__main__:debug
2020-04-16 11:28:24,988:INFO:155:__main__:info
2020-04-16 11:28:24,988:WARNING:156:__main__:warning
2020-04-16 11:28:24,988:ERROR:157:__main__:error
2020-04-16 11:28:24,988:CRITICAL:158:__main__:critical

Sortie standard de la borne:

__main__:DEBUG:-> debug
__main__:INFO:-> info
__main__:WARNING:-> warning
__main__:ERROR:-> error
__main__:CRITICAL:-> critical

Je pense que c'est une question / réponse particulièrement importante à faire référence au cas où vous auriez des problèmes avec UNSET: À propos de NOTSET dans la journalisation python Dieu merci pour la réponse et la question.

0
Pinocchio 16 avril 2020 à 16:36

Je veux partager le code que j'utilise, inspiré par la réponse acceptée:

import sys 
from pathlib import Path
def my_print(*args, filepath='~/my_stdout.txt'):
    filepath = Path(filepath).expanduser()
    # do normal print
    __builtins__['print'](*args, file=sys.__stdout__) #prints to terminal
    # open my stdout file in update mode
    with open(filepath, "a+") as f:
        # save the content we are trying to print
        __builtins__['print'](*args, file=f) #saves in a file

def collect_content_from_file(filepath):
    filepath = Path(filepath).expanduser()
    contents = ''
    with open(filepath,'r') as f:
        for line in f.readlines():
            contents = contents + line
    return contents

Notez le a+ pour pouvoir créer le fichier s'il n'existe pas déjà.

Notez que si vous souhaitez supprimer l'ancien contenu de votre my_stdout.txt personnalisé, vous devez supprimer le fichier et vérifier s'il existe:

    # remove my stdout if it exists
    os.remove(Path('~/my_stdout.txt').expanduser()) if os.path.isfile(Path('~/my_stdout.txt').expanduser()) else None

Je pense que cela devrait être tout.

1
Pinocchio 7 avril 2020 à 19:09

Vous pouvez temporairement rediriger stdout vers un objet de votre choix. L'exemple ci-dessous stocke les données imprimées dans une instance StringIO. Une fois le bloc du gestionnaire de contexte terminé, l'impression normale reprend et permet d'afficher certaines informations de débogage:

#! /usr/bin/env python3
import contextlib
import io


def main():
    file = io.StringIO()
    with contextlib.redirect_stdout(file):
        print('a')
        print('b')
        print('c')
    print(f'{file!r}\n{file.getvalue()!r}\n{file.getvalue()!s}')


if __name__ == '__main__':
    main()

Addendum:

Si vous souhaitez utiliser stdout comme d'habitude et toujours capturer ce qui y est imprimé, vous pouvez utiliser l'exemple suivant à la place. La classe Apply peut encapsuler plusieurs instances et dupliquer les appels de méthode sur chacune d'entre elles. Par conséquent, l'appel à redirect_stdout a été légèrement modifié:

#! /usr/bin/env python3
import contextlib
import io
import sys


def main():
    file = io.StringIO()
    with contextlib.redirect_stdout(Apply(sys.stdout, file)):
        print('a')
        print('b')
        print('c')
    print(f'{file!r}\n{file.getvalue()!r}\n{file.getvalue()!s}')


class Apply:

    def __init__(self, *args):
        self.__objects = args

    def __getattr__(self, name):
        attr = _Attribute(getattr(obj, name) for obj in self.__objects)
        setattr(self, name, attr)
        return attr


class _Attribute:

    def __init__(self, iterable):
        self.__attributes = tuple(filter(callable, iterable))

    def __call__(self, *args, **kwargs):
        return [attr(*args, **kwargs) for attr in self.__attributes]


if __name__ == '__main__':
    main()
1
Noctis Skytower 7 avril 2020 à 20:51

Vous pouvez utiliser le code suivant:

import sys
from builtins import print as builtin_print
myfile = "output.txt"
def print(*args):
    builtin_print(*args, file=sys.__stdout__)    # prints to terminal
    with open(myfile, "a+") as f:
        builtin_print(*args, file=f)    # saves in a file

Cela devrait redéfinir la fonction print afin qu'elle s'imprime dans stdout et dans votre fichier. Vous pouvez ensuite lire le fichier.

1
Noctis Skytower 7 avril 2020 à 20:40