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



我试图在字符串中获取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的内容放在字符串中。


如果你遇到像我这样的错误,这些可能是相关的:为什么__builtins__既是模块又是dictPython:What';__内置__和__内置__之间的区别是什么?

错误:

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

我读到的问题没有帮助:

  • 让文件在Python中可写可读
  • TypeError:应为str、字节或os。PathLike对象,而不是_io.TextIOWrapper
  • 在python中通过电子邮件发送未修改的打印语句中的内容

您可以使用以下代码:

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和您的文件中。然后您可以从文件中读取。

您可以临时将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}n{file.getvalue()!r}n{file.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}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()

我想分享我正在使用的代码,灵感来自公认的答案:

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

我研究了更多细节:

  • 为什么__builtins__既是模块又是dict
  • Python:What';__内置__和__内置__之间的区别是什么

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

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

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

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

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

日志内容:

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有问题:关于python日志中的NOTSET感谢上帝的回答和问题。

相关内容

最新更新