如何编写符合Pylint consider-using-with规则的open上下文管理器包装器
问题核心解答:open调用放在__enter__是更规范的实践
为什么__init__里写open会触发Pylint R1732报错
Pylint的这个报错不是误判:如果把资源分配(也就是open调用)放在构造函数中,一旦你实例化ManagedFile之后没有立刻放入with块使用,或者实例化过程中后续代码抛出异常,已经打开的文件句柄就会出现泄漏,不符合资源管理器的设计原则。
__enter__中执行open的合理性
把open放在__enter__是标准的上下文管理器实现逻辑:资源的分配和释放完全和上下文的生命周期绑定,只有真正进入with块的时候才会打开文件,只要正确使用with语法就不会出现资源泄漏,完全符合Python的设计规范,不需要担心实践正确性。
针对需要提前获取文件句柄初始化基类的场景解决方案
如果你确实有提前拿到文件对象初始化基类的需求,有两种可行方案:
- 方案1:在__init__的open语句后添加局部忽略注释
# pylint: disable=consider-using-with,前提是你必须保证所有ManagedFile的实例都会被放到with块中使用,或者你自己实现了可靠的兜底关闭逻辑。 - 方案2:调整基类的初始化逻辑,把依赖文件句柄的初始化操作延后到__enter__中执行,不需要在__enter__中调用基类构造函数,仅调用基类的初始化方法即可,符合类继承的设计规范。
现有代码的可优化点
- 不要关闭用户传入的已有文件对象、以及
-对应的标准输入输出对象:现有两个版本的__exit__都统一调用了close(),会导致标准流被意外关闭,后续无法正常使用,应该添加标记位,仅关闭本类内部调用open打开的文件。 - 逻辑冗余:现有代码中已经判断
isinstance(file_in, str),后续又判断file_in is None永远不会成立,可以删除这部分无效判断。 - 避免重复调用上下文方法:文件对象本身的__enter__就是返回自身,不需要重复调用,直接返回
self.file_handle即可。
改造为完整文件对象的方案
只需要在类中添加__getattr__方法,把所有未在本类中定义的属性和方法转发到内部的file_handle即可,示例:
def __getattr__(self, name): return getattr(self.file_handle, name)
添加后就可以直接对ManagedFile实例调用read()、write()、seek()等所有文件对象原生方法,使用体验和原生文件对象完全一致。
修复后的参考实现(__enter__分配资源版本)
import io import sys class ManagedFile: '''Manage a file, which could be an unopened filename, could be '-' for stdin/stdout, or could be an existing filehandle''' def __init__(self, file_in, handle_default=sys.stdin, open_kwargs=None): self._is_managed = False self.file_handle = None self._open_kwargs = open_kwargs or {'mode': 'r'} self._file_in = file_in self._handle_default = handle_default if isinstance(file_in, io.IOBase): self.file_handle = file_in elif isinstance(file_in, str): if file_in == "-": self.file_handle = handle_default else: self._is_managed = True else: raise TypeError('File specified must be string or file object') def __enter__(self): if self._is_managed and self.file_handle is None: self.file_handle = open(self._file_in, **self._open_kwargs) return self.file_handle def __exit__(self, err_type, err_value, traceback): # 仅关闭自己打开的托管文件 if self._is_managed and self.file_handle is not None: self.file_handle.close() self.file_handle = None self._is_managed = False # 透传异常返回值 return False def __getattr__(self, name): return getattr(self.file_handle, name) def handle(self): '''Return handle of file that was opened''' return self.file_handle
内容的提问来源于stack exchange,提问作者Vercingatorix
相关产品推荐
相关产品推荐

