繁体   English   中英

为什么模块不能是上下文管理器(对于“with”语句)?

[英]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 步失败,因此会截断列表):

  1. 上下文表达式(在with_item 中给出的表达式)被评估以获得上下文管理器。
  2. 上下文管理器的__exit__()被加载以备后用。
  3. 上下文管理器的__enter__()方法被调用。
  4. 等等...

据我了解,没有理由__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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM