同时打印到屏幕和写入文件

Posted

技术标签:

【中文标题】同时打印到屏幕和写入文件【英文标题】:Printing to screen and writing to a file at the same time 【发布时间】:2012-03-08 11:14:47 【问题描述】:

我在网上找到了一些通常可以运行的代码,但我想在同一个程序中多次使用它(将不同的内容写入不同的文件,同时始终打印到屏幕上)。

也就是说,当它关闭时,我认为 sys.stdout 关闭了,所以根本打印,并且再次使用该类失败。我尝试重新导入 sys 和其他愚蠢的东西,但我无法让它工作。

这是网站和代码 groups.google.com/group/comp.lang.python/browse_thread/thread/d25a9f5608e473af/

import sys

class MyWriter:

    def __init__(self, stdout, filename):
        self.stdout = stdout
        self.logfile = file(filename, 'a')

    def write(self, text):
        self.stdout.write(text)
        self.logfile.write(text)

    def close(self):
        self.stdout.close()
        self.logfile.close()

writer = MyWriter(sys.stdout, 'log.txt')
sys.stdout = writer

print 'test' 

【问题讨论】:

什么时候关闭?我没有看到任何关闭的东西。 【参考方案1】:

你试图重现 Python 标准库做得很好的东西;请检查logging module。

使用此模块,您可以完全按照自己的意愿进行操作,而且方式更加简单、标准和可扩展。您可以按照以下方式进行操作(此示例是从logging cookbook 复制/粘贴):

假设您要登录到控制台并使用不同的消息进行归档 格式和在不同的情况下。假设您要记录消息 具有 DEBUG 和更高级别的文件,以及级别的那些消息 INFO 和更高的控制台。我们还假设文件应该 包含时间戳,但控制台消息不应该。这是如何做 你可以做到这一点:

import logging

# set up logging to file - see previous section for more details
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                    datefmt='%m-%d %H:%M',
                    filename='/temp/myapp.log',
                    filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# set a format which is simpler for console use
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger().addHandler(console)

# Now, we can log to the root logger, or any other logger. First the root...
logging.info('Jackdaws love my big sphinx of quartz.')

# Now, define a couple of other loggers which might represent areas in your
# application:

logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')

logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

当你运行它时,你会在控制台上看到

root        : INFO     Jackdaws love my big sphinx of quartz.
myapp.area1 : INFO     How quickly daft jumping zebras vex.
myapp.area2 : WARNING  Jail zesty vixen who grabbed pay from quack.
myapp.area2 : ERROR    The five boxing wizards jump quickly.

在文件中你会看到类似

10-22 22:19 root         INFO     Jackdaws love my big sphinx of quartz.
10-22 22:19 myapp.area1  DEBUG    Quick zephyrs blow, vexing daft Jim.
10-22 22:19 myapp.area1  INFO     How quickly daft jumping zebras vex.
10-22 22:19 myapp.area2  WARNING  Jail zesty vixen who grabbed pay from quack.
10-22 22:19 myapp.area2  ERROR    The five boxing wizards jump quickly.

如您所见,DEBUG 消息仅显示在文件中。另一个 消息被发送到两个目的地。

此示例使用控制台和文件处理程序,但您可以使用任何 您选择的处理程序的数量和组合。

【讨论】:

+1 登录屏幕和文件的所有 SO 问题的最佳答案 我喜欢这个答案,但我不确定这是原始发帖人所要求的。 当完全复制上面的代码时,logging.info('Jackdaws love my big sphinx of quartz.') 之后我收到以下错误:... File "C:\Anaconda2\lib\logging_init_.py",第 467 行,格式为 record.asctime = self.formatTime(record, self.datefmt) 文件“C:\Anaconda2\lib\logging_init_.py”,第 425 行,格式为 s = 时间。 strftime(datefmt, ct) ValueError: Invalid format string 谁能帮忙?【参考方案2】:

轻松使用 Python 3.3 及更高版本

从 Python 3.3 开始,由于 logging.basicConfig 现在接受 handlers = 参数,这样做变得非常容易。

import logging

level    = logging.INFO
format   = '  %(message)s'
handlers = [logging.FileHandler('filename.log'), logging.StreamHandler()]

logging.basicConfig(level = level, format = format, handlers = handlers)
logging.info('Hey, this is working!')

但是请注意,某些 Python 模块也可能会将日志消息发布到 INFO 级别。

这是create a custom logging level 的方便之处,例如称为OK,高于默认INFO 级别5 级,低于默认WARNING 级别5 级。

【讨论】:

也可以单独自定义每个处理程序,如***.com/a/9321890 中给出的那样,并使用logging.basicConfig 中的handlers=handlers kwarg,如下所示。【参考方案3】:

删除正在执行您明确表示不希望执行的操作的行:close() 的第一行,它关闭标准输出。

【讨论】:

【参考方案4】:

我知道这是一个老问题,最好的答案就是将logging 用于其预期目的,但我只想指出,如果您只关心影响呼叫特别 em> 到print(而不是与sys.stdout 的其他交互),并且您只想将几行粘贴到一些旧的一次性脚本中,没有什么能阻止您简单地将名称重新分配给写入到的不同函数两个不同的文件,因为print 是 Python 3+ 中的一个函数。上帝保佑,你甚至可以使用带有 or 链的 lambda 来获得最快、最脏的解决方案:

old_print = print
log_file = open("logfile.log", "a")
print = lambda *args, **kw: old_print(*args, **kw) or old_print(*args, file=log_file, **kw)
print("Hello console and log file")
# ... more calls to print() ...
log_file.close()

或者对于真正的即发即弃:

import atexit
old_print = print
log_file = open("logfile.log", "a")
atexit.register(log_file.close)
print = lambda *args, **kw: old_print(*args, **kw) or old_print(*args, file=log_file, **kw)
# ... do calls to print(), and you don't even have to close the file afterwards ...

假设程序正常退出它工作正常,但请不要在生产代码中使用它,只需使用logging :)

编辑:如果您重视某种形式的结构并希望实时写入日志文件,请考虑以下内容:

from typing import Callable

def print_logger(
    old_print: Callable, 
    file_name: str,
) -> Callable:
    """Returns a function which calls `old_print` twice, specifying a `file=` on the second call.
    
    Arguments:
        old_print: The `print` function to call twice.
        file_name: The name to give the log file.
    """
    def log_print(*args, **kwargs):
        old_print(*args, **kwargs)
        with open(file_name, "a") as log_file:
            old_print(*args, file=log_file, **kwargs)
    return log_print

然后调用如下:

print = print_logger(print, "logs/my_log.log")

【讨论】:

【参考方案5】:

也就是说,当它关闭时,我认为 sys.stdout 关闭,所以打印 完全没有,并且再次使用这个类失败了。我尝试重新导入系统,并且 其他愚蠢的东西,但我无法让它工作。

要回答您的问题,您不应该关闭标准输出。 python 解释器在启动时打开 stdout、stdin 和 stderror。为了打印工作,解释器需要打开标准输出。加载模块后,重新导入 sys 不会做任何事情。您需要重新加载模块。在这种特殊情况下,我不确定重新加载是否会解决问题,因为 sys.stdout 允许将标准输出用作文件对象。

此外,我认为您的代码中存在可能导致打印的错误 休息。在第 2 行中,您将 MyWriter 对象分配给 sys.stdout。这可以通过在垃圾收集器删除未使用的标准输出文件对象时关闭标准输出。

writer = MyWriter(sys.stdout, 'log.txt')
sys.stdout = writer

【讨论】:

以上是关于同时打印到屏幕和写入文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Popen 同时写入标准输出和日志文件?

在将输出写入文件时使用多处理显示来自子进程的实时输出

打开管道的缓冲问题,写入文件

django日志写入文件

python日志打印和写入并发简易版本实现

如何同时读取和写入数据到同一个文件