[英]Why can't a module be a context manager (to a 'with' statement)?
假设我们有以下mod.py
:
def __enter__():
print("__enter__<")
def __exit__(*exc):
print("__exit__< {0}".format(exc))
class cls:
def __enter__(self):
print("cls.__enter__<")
def __exit__(self, *exc):
print("cls.__exit__< {0}".format(exc))
以及它的以下用途:
import mod
with mod:
pass
我收到一个错误:
Traceback (most recent call last):
File "./test.py", line 3, in <module>
with mod:
AttributeError: __exit__
根据文档, with
语句的文档应按如下方式执行(我相信它在第 2 步失败,因此会截断列表):
- 上下文表达式(在with_item 中给出的表达式)被评估以获得上下文管理器。
- 上下文管理器的
__exit__()
被加载以备后用。- 上下文管理器的
__enter__()
方法被调用。- 等等...
据我了解,没有理由__exit__
。 有什么我错过的东西使模块无法作为上下文管理器工作吗?
__exit__
是一个特殊的方法,所以 Python 在类型上查找它。 module
类型没有这样的方法,这就是失败的原因。
请参阅 Python 数据模型文档的特殊方法查找部分:
对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。
请注意,这适用于所有特殊方法。 例如,如果您向模块添加了__str__
或__repr__
函数,则在打印模块时也不会调用它。
Python 这样做是为了确保类型对象也是可散列和可表示的; 如果 Python没有这样做,那么当为__hash__
定义了__hash__
方法时,尝试将类对象放入字典将失败(因为该方法期望为self
传入一个实例)。
由于@Martijn Pieters answer 中所述的原因,您无法轻松做到。 然而,通过一些额外的工作,它是可能的,因为sys.modules
的值不必是内置模块类的实例,它们可以是您自己的自定义类的实例,具有上下文管理器所需的特殊方法。
这是将其应用于您想要做的事情。 鉴于以下mod.py
:
import sys
class MyModule(object):
def __enter__(self):
print("__enter__<")
def __exit__(self, *exc):
print("__exit__> {0}".format(exc))
# replace entry in sys.modules for this module with an instance of MyModule
_ref = sys.modules[__name__]
sys.modules[__name__] = MyModule()
以及它的以下用途:
import mod
with mod:
print('running within context')
将产生此输出:
__enter__<
running within context
__exit__> (None, None, None)
有关为什么需要_ref
信息,请参阅此问题。
一个比 Martineau 提出的更柔和的版本,少一点争论:
import sys
class CustomModule(sys.modules[__name__].__class__):
"""
Custom module
"""
def __enter__(self):
print('enter')
def __exit__(self, *args, **kwargs):
print('exit')
sys.modules[__name__].__class__ = CustomModule
而不是替换模块(这可能会导致无数问题),只需将类替换为从原始类继承的类。 这样,原始模块对象被保留,不需要另一个 ref(防止垃圾收集),它可以与任何自定义导入器一起使用。 请注意一个重要的事实,即在执行模块代码之前创建了一个模块对象并将其添加到 sys.modules 中。
注意使用这种方式,可以添加任何魔法方法
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.