Suppressor类仅在Notebook生效,命令行环境下失效问题求助
问题描述
我实现了一个Suppressor类用于抑制并记录RDKit方法的输出,代码如下:
import sys import traceback import warnings from rdkit import RDLogger from rdkit import Chem class Suppressor: def __init__(self, log_file): self.log_file = log_file self.stdout = sys.stdout self.stderr = sys.stderr def __enter__(self): sys.stdout = self sys.stderr = self def __exit__(self, exception_type, value, tb): sys.stdout = self.stdout sys.stderr = self.stderr if exception_type is not None: raise Exception(f"Got exception: {exception_type} {value} {traceback}") def write(self, x): with open(self.log_file, "a") as log: log.write(x)
使用方式:
with Suppressor("error.log"): Chem.MolFromSmiles('CO(C)C')
该类在Notebook环境中可正常抑制并记录Chem.MolFromSmiles('CO(C)C')的输出,但在命令行运行脚本时,无法抑制和记录该方法的输出,不过print语句的输出能被正常抑制。测试其他抛出错误的函数时,类功能正常,需排查此问题。
问题原因
RDKit的MolFromSmiles等方法的输出并非通过Python的sys.stdout/sys.stderr输出,而是直接调用底层C代码的标准输出/错误流。在Notebook环境中,Python会将底层系统流重定向到Notebook的输出系统,因此Suppressor类的Python级流替换能生效;但在命令行环境中,底层C流与Python的sys流相互独立,仅替换Python级别的流无法影响底层C++的输出。
解决方案
方法1:使用RDKit自带日志控制工具
RDKit提供了专门的RDLogger模块,可直接关闭或重定向其日志输出,无需手动替换流:
from rdkit import RDLogger # 关闭所有RDKit日志输出 RDLogger.DisableLog('rdApp.*') # 或指定日志输出文件及级别 logger = RDLogger.logger() logger.SetLogFile('error.log') logger.SetLevel(RDLogger.ERROR) # 仅记录错误级别日志
配置完成后,调用Chem.MolFromSmiles的相关输出会被写入指定文件,不会在命令行显示。
方法2:重定向系统级文件描述符
若需同时处理Python代码和底层C++代码的输出,可通过重定向系统级文件描述符实现。修改Suppressor类如下:
import sys import traceback import os from contextlib import contextmanager @contextmanager def suppress_fd(fd, log_file): # 保存原始文件描述符 original_fd = os.dup(fd) # 打开日志文件 log = open(log_file, 'a') # 将目标文件描述符重定向到日志文件 os.dup2(log.fileno(), fd) try: yield finally: # 恢复原始文件描述符 os.dup2(original_fd, fd) log.close() os.close(original_fd) class Suppressor: def __init__(self, log_file): self.log_file = log_file self.stdout = sys.stdout self.stderr = sys.stderr self.fd_suppressors = [suppress_fd(1, log_file), suppress_fd(2, log_file)] def __enter__(self): sys.stdout = self sys.stderr = self # 启动文件描述符重定向 for suppressor in self.fd_suppressors: suppressor.__enter__() def __exit__(self, exception_type, value, tb): sys.stdout = self.stdout sys.stderr = self.stderr # 恢复文件描述符 for suppressor in reversed(self.fd_suppressors): suppressor.__exit__(exception_type, value, tb) if exception_type is not None: raise Exception(f"Got exception: {exception_type} {value} {traceback.format_exc()}") def write(self, x): with open(self.log_file, "a") as log: log.write(x)
该版本同时替换了Python级别的流和系统级文件描述符,可覆盖Python代码及底层C++代码的输出抑制与记录需求。
内容的提问来源于stack exchange,提问作者user17230397
相关产品推荐
相关产品推荐

