__enter__ 和 __exit__

问题描述 投票:0回答:3

我没有成功地尝试在类级别运行神奇的

with
语句方法
__enter__
__exit__

class Spam():

    @classmethod
    def __enter__(cls):
        return cls

    @classmethod
    def __exit__(cls, typ, value, tb):
        cls.cleanup_stuff()


with Spam:
    pass

但是,这会导致

AttributeError
:

Traceback (most recent call last):
  File "./test.py", line 15, in <module>
    with Spam:
AttributeError: __exit__

是否可以在类级别使用

__enter__
__exit__
方法?

python python-3.x with-statement class-method
3个回答
15
投票

__enter__
__exit__
是特殊方法,因此只有在 在对象类型上定义时才能正常工作,而不是在其实例字典中定义。

现在

Spam
type
的实例,
type(Spam).__enter__
type(Spam).__exit__
不存在。因此你会得到一个属性错误。

要实现此目的,需要在要使用的类的元类上声明这些方法。示例:

class Spam(type):

    def __enter__(cls):
        print('enter')
        return cls

    def __exit__(cls, typ, value, tb):
        print('exit')

class Eggs(metaclass=Spam):
    pass

with Eggs:
    pass

现在

Eggs
Spam
的实例(
type(Eggs)
==
Spam
,因此
type(Eggs).__enter__
type(Eggs).__exit__
确实存在)。

然而,定义一个元类只是为了使用它的实例作为上下文管理器似乎有点过分了。从您的示例开始,更直接的解决方案就是使用

with Spam():
    pass

或者如果您想稍后重用同一实例:

spam = Spam()
with spam:
    pass

0
投票

CPython 似乎不会调用像

instance.__exit__
这样的绑定方法,它会查找实例类型,执行类似
type(instance).__dict__['__exit__']
的操作而不是调用它。并且由于
type(Spam)
是一个特殊的
type
对象(不是
Spam
本身),因此它不包含
__exit__
方法。

我尝试使用元类来解决这个问题,但没有成功。

__getattr__
也不起作用。

参见这里:https://github.com/python/cpython/blob/2545fdbd4b4a6a77b132fccf816578f59b609be5/Objects/typeobject.c#L1362

  • Py_TYPE 类似于
    type(self)
  • _PyType_LookupId 走过
    type(self).__dict__
    (这里没有
    __getattr__
    调用)

Python 2 实现有所不同,但关于获取

type(self)
的主要思想也适用于它


0
投票

您可以组合

@classmethod
@contextlib.contextmanager
来创建充当上下文管理器的类方法:

from __future__ import annotations

from contextlib import contextmanager
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Iterator, Self

class Spam:
    @classmethod
    @contextmanager
    def context(cls) -> Iterator[type[Self]]:
        try:
            # Setup here
            yield cls
        finally:
            cls.cleanup_stuff()

with Spam.context():
    pass

请注意,装饰器的顺序很重要。

参考:

© www.soinside.com 2019 - 2024. All rights reserved.