首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么模块不能成为上下文管理器(对于“with”语句)?

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

Stack Overflow用户
提问于 2016-11-15 09:09:27
回答 3查看 1.2K关注 0票数 5

假设我们有以下mod.py

代码语言:javascript
复制
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))

以及它的下列用途:

代码语言:javascript
复制
import mod

with mod:
    pass

我收到一个错误:

代码语言:javascript
复制
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__。有什么让模块不能作为上下文管理器工作的东西我已经错过了?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-11-15 09:16:09

__exit__是一种特殊的方法,所以Python会在类型上查找它。module类型没有这样的方法,这就是失败的原因。

请参阅Python文档的 section

对于自定义类,只有在对象的类型上定义而不是在对象的实例字典中定义时,才能保证对特定方法的隐式调用才能正确工作。

请注意,这适用于所有特殊方法。例如,如果将__str____repr__函数添加到模块中,则在打印模块时也不会调用该函数。

Python这样做是为了确保类型对象也是可接受的和可表示的;如果Python没有这样做,那么当为该类定义了__hash__方法时,尝试将类对象放入字典中就会失败(因为该方法期望为self传递一个实例)。

票数 7
EN

Stack Overflow用户

发布于 2016-11-15 10:25:56

由于@Martijn answer中所述的原因,您不能轻松地做到这一点。但是,只要做一些额外的工作就可以了,因为sys.modules中的值不必是内置模块类的实例,它们可以是您自己的自定义类的实例,并具有上下文管理器所需的特殊方法。

这就是你想要做的事情。给定以下mod.py

代码语言:javascript
复制
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()

以及它的下列用途:

代码语言:javascript
复制
import mod

with mod:
    print('running within context')

将产生此输出:

代码语言:javascript
复制
__enter__<
running within context
__exit__> (None, None, None)

有关为什么需要this的信息,请参阅_ref问题。

票数 2
EN

Stack Overflow用户

发布于 2020-12-02 17:11:13

一个比马丁诺提出的更柔和的版本,稍微少了一点争论:

代码语言:javascript
复制
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

与其替换模块(wich可能会导致无数问题),不如将类替换为从原始类继承的一个类。这样,保留原来的模块对象,就不需要另一个ref (防止垃圾收集),它将与任何自定义导入程序一起工作。注意一个重要的事实,模块对象是在执行模块代码之前创建并添加到sys.modules 中的。

请注意,使用这种方法,您可以添加任何魔术方法。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40605865

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档