如何使已打开的文件可读(例如 sys.stdout)?

Posted

技术标签:

【中文标题】如何使已打开的文件可读(例如 sys.stdout)?【英文标题】:How does one make an already opened file readable (e.g. sys.stdout)? 【发布时间】:2020-07-19 22:24:39 【问题描述】:

我试图在字符串中获取sys.stdout 的内容。我尝试了明显的:

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)

但这给出了错误:

Exception has occurred: UnsupportedOperation
not readable

那么我该如何更改已打开文件的权限?

我试过了:

    sys.stdout.mode = 'r'

但这仍然给出同样的错误......

其他可行的方法是以独立于硬件的方式让我获得stdout 的名称/路径。


另一件可行的事情是让我在我运行主脚本后将sys.stdout 的内容放在一个字符串中。


如果你遇到像我这样的错误,这些可能是相关的:why __builtins__ is both module and dictPython: What's the difference between __builtin__ and __builtins__?

错误:

line 37, in my_print
    __builtins__["print"](*args, file=f)  # saves to file
TypeError: 'module' object is not subscriptable

我读过但没有帮助的问题:

Making File Writable and Readable in Python TypeError: expected str, bytes or os.PathLike object, not _io.TextIOWrapper Send the contents from unmodified print statement by e-mail in python

【问题讨论】:

您无法获取标准输出的内容。你所能做的就是写它,它不会保存它以供回读。 如果您愿意,可以将标准输出设置为您制作的具有读写权限的特定文件。 @trigangle 应该可以工作!只要标准的python字符串函数仍然有效! 只有一件事:您可以使用print("blob", stdout=<insert file>) 打印到特定文件。 它将无法做到这一点。您需要更改打印功能以打印到sys.__stdout__ 和您的特定文件。 【参考方案1】:

您可以使用以下代码:

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

这应该重新定义print 函数,以便它打印到stdout 和您的文件。然后您可以从文件中读取。

【讨论】:

这条线是做什么的:__builtins__.print(*args, file=sys.__stdout__)?你为什么不做print = __builtins__.print(*args, file=sys.__stdout__)之类的事情? __builtins__ 正在调用原始打印功能。原因是一旦我重新定义了打印功能,旧功能就消失了。因此,调用__builtins__.print 允许我回调原始打印函数。 file= 处理程序告诉打印函数打印到正确的文件。 sys.__stdout__ 是终端的原始标准输出。因此,当您打印时,您将打印到终端并将所有内容保存在文件中。 这也是我的方法——我在这里唯一可能做的不同的事情是在脚本运行时opening 文件并在它完成时关闭它。我想这取决于你打电话给print 的次数。 @trigangle +1 太棒了!我一直试图获得指向原始打印的指针,但陷入循环或让__future_ 向我抱怨。不知道__builtins__ @trigangle 太棒了!您记住了a+,以便在文件不存在时创建该文件。【参考方案2】:

您可以暂时将stdout 重定向到您选择的对象。下面显示的示例将打印数据存储在StringIO 实例中。一旦上下文管理器块结束,正常打印恢复并允许显示一些调试信息:

#! /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\nfile.getvalue()!r\nfile.getvalue()!s')


if __name__ == '__main__':
    main()

附录:

如果您希望像平常一样使用stdout 并仍然捕获打印到它的内容,您可能需要改用以下示例。 Apply 类可以包装多个实例并在所有实例中重复方法调用。因此,对redirect_stdout 的调用稍作修改:

#! /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\nfile.getvalue()!r\nfile.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()

【讨论】:

我想我想要的是能够重定向并正常打印到终端。像这样工作吗? @Pinocchio 原始代码没有按照您的要求运行。但是,如果您阅读了原始答案下方添加的附录,则提供了允许正常重定向和打印到终端的新代码。【参考方案3】:

我想分享我正在使用的代码,受接受的答案的启发:

def my_print(*args, filepath="~/my_stdout.txt"):
    """Modified print statement that prints to terminal/scree AND to a given file (or default).

    Note: import it as follows:

    from utils.utils import my_print as print

    to overwrite builtin print function

    Keyword Arguments:
        filepath str -- where to save contents of printing (default: '~/my_stdout.txt')
    """
    import sys
    from builtins import print as builtin_print
    filepath = Path(filepath).expanduser()
    # do normal print
    builtin_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
        builtin_print(*args, file=f)  # saves to file

请注意a+ 以便能够在文件不存在时创建它。

请注意,如果您想删除自定义my_stdout.txt 的旧内容,您需要删除该文件并检查它是否存在:

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

我想应该就这些了。


编辑:

我遇到了一个错误:

line 37, in my_print
    __builtins__["print"](*args, file=f)  # saves to file
TypeError: 'module' object is not subscriptable

我查看了更多细节:

why __builtins__ is both module and dict Python: What's the difference between __builtin__ and __builtins__?

并了解到__builtins__ 似乎不可靠(由于 python 实现细节)。

似乎访问内置函数最可靠的方法是使用导入,所以我将其返回给原始回答者给我的代码。

【讨论】:

【参考方案4】:

我之前对这个问题的回答并没有我想象的那么好(https://***.com/a/61087617/3167448)。我认为这个问题的真正答案是简单地使用记录器。直到最近我才知道记录器是什么,但它们要好得多。

最好创建一个记录器对象,将字符串发送到日志文件和标准输出。它甚至允许您根据阈值级别更精细地路由消息。代码如下:

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://***.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://***.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')

日志内容:

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

终端标准输出:

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

我觉得这是一个特别重要的问题/答案,以防万一您遇到UNSET 的问题:About NOTSET in python logging 感谢上帝提供的答案和问题。

【讨论】:

以上是关于如何使已打开的文件可读(例如 sys.stdout)?的主要内容,如果未能解决你的问题,请参考以下文章

python中模拟进度条

如何使已选择的选项不显示在另一个选择选项下拉 JAVASCRIPT/ANGULARJs

python核心编程 第二天

为啥 sys.stdout.write 被调用两次?

python sys.stdin,sys.stdout,sys.stderr

将换行符写入 sys.stdout 时,使 Python 停止发出回车符