同时在屏幕上打印和写入文件

78 投票
5 回答
76036 浏览
提问于 2025-04-17 13:09

我在网上找到了一些代码,基本上可以用,但我想在同一个程序里多次使用它(写不同的内容到不同的文件,同时还要一直在屏幕上打印信息)。

也就是说,当它关闭的时候,我觉得 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' 

5 个回答

6

我知道这个问题已经很老了,最好的答案就是按照它的本意使用 logging,但我想提一下,如果你只关心影响到 print 的调用(而不是其他与 sys.stdout 的交互),而且你只是想把几行代码粘贴到一些旧的临时脚本里,其实你可以直接把 print 这个名字重新指向一个不同的函数,这个新函数可以写入两个不同的文件,因为在 Python 3 及以上版本中,print 是一个函数。你甚至可以,天哪,使用一个带有 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")
68

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

199

你正在尝试做一些事情,但其实Python的标准库已经很好地解决了这个问题。建议你看看日志模块

通过这个模块,你可以轻松实现你想要的功能,而且方式更简单、标准且可扩展。你可以按照以下步骤进行操作(这个例子是从日志食谱复制过来的):

假设你想要将日志记录到控制台和文件中,并且希望它们的消息格式不同,记录的情况也不同。比如,你希望将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消息只会出现在文件中。其他消息则会同时发送到两个地方。

这个例子使用了控制台和文件处理器,但你可以根据需要使用任意数量和组合的处理器。

撰写回答