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

时间:2012-02-17 01:35:06

标签: python

我发现一些在线的代码通常有效,但我想在同一个程序中多次使用它(将不同的东西写入不同的文件,同时仍然一直打印到屏幕上)。

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

答案 0 :(得分:111)

你试图通过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 :(得分:19)

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

答案 2 :(得分:1)

删除你明确表示不想做的事情的行:close()的第一行,它关闭stdout。

答案 3 :(得分:0)

  

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

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

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

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

答案 4 :(得分:0)

我知道这是一个老问题,最好的答案就是将 logging 用于其预期目的,但我只想指出,如果您只关心影响调用 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")