同时将内容打印到屏幕并写入文件

75

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

也就是说,当它关闭时,我认为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' 

什么时候关闭?我没有看到任何东西在那里关闭。 - Ignacio Vazquez-Abrams
5个回答

194
您正在试图复制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消息只会显示在文件中。其他消息会同时发送到两个目标。
此示例使用控制台和文件处理程序,但您可以使用任意数量和组合的处理程序。

12
在所有 Stack Overflow 问题中,+1 最佳答案是关于在屏幕和文件中记录日志的。 - user966588
2
我喜欢这个答案,但我不确定这是否是原帖作者所询问的。 - Zachary Kraus
当我完全复制上面的代码后,在logging.info('Jackdaws love my big sphinx of quartz.')之后,我得到了以下错误: ... 文件“C:\ Anaconda2 \ lib \ logging \ __ init__.py”,第467行,在格式化时 记录.asctime = self.formatTime(record,self.datefmt) 文件“C:\ Anaconda2 \ lib \ logging \ __ init__.py”,第425行,在formatTime中 s = time.strftime(datefmt,ct) ValueError:无效的格式字符串有人能帮忙吗? - riccio777
1
这个回答太长了,言归正传。我需要在日志模块中设置什么,特别是在记录到文件和终端方面的级别? - Charlie Parker
回答自己,参见:https://dev59.com/CGox5IYBdhLWcg3wQSOp#40909549 获取快速答案。 - Charlie Parker

67

使用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级别。

这就是创建自定义日志级别变得方便的地方,例如称为OK的级别,比默认的INFO级别高5个级别,并且比默认的WARNING级别低5个级别。(创建自定义日志级别)


1
可以根据 https://dev59.com/CGox5IYBdhLWcg3wQSOp#9321890 中的说明单独自定义每个处理程序,并在此处显示使用 handlers=handlers kwarg 的 logging.basicConfig - irene
这个答案似乎在运行pdb时无效:https://dev59.com/Ab74oIgBc1ULPQZF9FuA 如何解决? - Charlie Parker

5

我知道这是一个老问题,最好的答案是只需使用logging来满足其预期目的,但我想指出的是,如果您只关心对print调用产生影响(而不是与sys.stdout的其他交互作用),并且只想将几行代码粘贴到一些旧的一次性脚本中,那么没什么阻止你将名称重新分配给写入两个不同文件的不同函数,因为print在 Python 3+中是一个函数。你甚至可以使用lambda和or链进行最快、最脏的解决方案:

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

3

移除那行明确表示你不想执行的操作:close()函数中关闭stdout的第一行。


0
换句话说,当它关闭时,我认为 sys.stdout 也被关闭了,因此打印任何东西并再次使用这个类都会失败。我尝试重新导入 sys 和其他愚蠢的东西,但是我无法使它正常工作。
回答您的问题,您不应该关闭 stdout。Python 解释器在启动时打开 stdout、stdin 和 stderror。为了使 print 正常工作,解释器需要保持 stdout 开着。重复导入 sys 在一个模块已经被加载后不会产生任何效果,您需要重新载入模块。在这种情况下,我不确定重新载入会修复问题,因为 sys.stdout 允许将 stdout 用作文件对象。
另外,我认为您的代码中可能有一个 bug 导致 print 失败。在第二行,您将 MyWriter 对象分配给 sys.stdout。当垃圾收集器删除未使用的 stdout 文件对象时,这可能会关闭 stdout。
writer = MyWriter(sys.stdout, 'log.txt')
sys.stdout = writer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接