我最近一直在研究 Python 的 contextmanager(更具体地说,Python 3 的 contextlib 或其向后移植的 contextlib2),我想知道将它们编写为类与函数相比有何优点/缺点?
它们似乎都以相同的方式运行并以相同的方式处理异常。有很多很酷的实用程序,例如 ExitStack(),但这些实用程序似乎可以在编写为类或函数的上下文管理器中实现。因此,我正在努力寻找一个很好的理由来解释为什么人们想要将上下文管理器详细地编写为一个类,而它们可以被编写为一个函数并且只需使用 contextmanager 装饰器即可。
这是我写的一个简单的例子,展示两者做同样的事情:
# !/usr/bin/python -u
# encoding: utf-8
from contextlib import contextmanager
# Function-based
@contextmanager
def func_custom_open(filename, mode):
try:
f = open(filename, mode)
yield f
except Exception as e:
print(e)
finally:
f.close()
# Class-based
class class_custom_open(object):
def __init__(self, filename, mode):
self.f = open(filename, mode)
def __enter__(self):
return self.f
def __exit__(self, type, value, traceback):
self.f.close()
if __name__ == '__main__':
# Function-based
with func_custom_open('holafile_func.txt', 'w') as func_f:
func_f.write('hola func!')
# Class-based
with class_custom_open('holafile_class.txt', 'w') as class_f:
class_f.write('hola class!')
如果你不需要使用语法的“详细”类,你就不需要它,就这么简单。
两者都存在的原因是使用类的方式是上下文管理器在该语言中工作的实际方式。任何在其类中具有
__enter__
和 __exit__
方法的对象都可以用作上下文管理器。
使用
@contextmanager
并允许将上下文管理器声明为函数的方式只是Python标准库中的一种实用工具。装饰器生成的是一个具有这两种方法的对象。
将上下文管理器编写为类可能会更紧凑的一种情况是,用作上下文管理器的对象也是您控制下的类,然后您可以更好地将运行
__enter__
和 __exit__
集成到它的生命周期。例如,看到可以用作装饰器或上下文管理器的对象并不罕见(我想到了unittest.mock.patch
)。对于用户来说,这听起来只是“魔法”,但实现非常清晰且定义明确:在此类对象的类中,它充当上下文管理器的逻辑位于 __enter__
/__exit__
,并且实现装饰器行为的逻辑位于 __call__
方法上。
基于函数的上下文管理器更简单、更容易阅读。通常这将是我的第一选择。
选择基于类的上下文管理器的一个原因已经在上面 @jsbueno 的回答中陈述了。也就是说,当您希望它不仅仅是一个上下文管理器时。
使用基于类的另一个原因是当您允许上下文管理器被子类化并进一步扩展时。