Python:标准函数和上下文管理器?

Python: standard function and context manager?

在python中,有许多函数既可以用作标准函数,也可以用作上下文管理器。例如 open() 可以被称为:

my_file=open(filename,'w')

with open(filename,'w') as my_file:

两者都为您提供了一个 my_file 对象,可以用来做任何您需要做的事情。一般来说,后者更可取,但有时也可能想做前者。

我已经弄清楚如何编写上下文管理器,方法是使用 __enter____exit__ 函数创建 class 或使用 @contextlib.contextmanager 函数上的装饰器和 yield 而不是 return。但是,当我这样做时,我不能再直接使用该函数 - 例如,使用装饰器,我得到一个 _GeneratorContextManager 对象而不是想要的结果。当然,如果我将它作为 class,我只会得到一个生成器 class 的实例,我认为这本质上是相同的。

那么我如何设计一个函数(或 class)既可以作为函数返回对象,也可以作为上下文管理器返回 _GeneratorContextManager 等?

编辑:

例如,假设我有如下函数(已高度简化):

def my_func(arg_1,arg_2):
    result=arg_1+arg_2
    return my_class(result)

因此该函数接受多个参数,对它们进行处理,并使用这些处理的结果来初始化 class,然后 returns。最终结果是我有一个 my_class 的实例,就像我调用 open 时会有一个 file 对象一样。如果我希望能够将这个函数用作上下文管理器,我可以这样修改它:

@contextlib.contextmanager
def my_func(arg_1,arg_2):
    result=arg_1+arg_2 # This is roughly equivalent to the __enter__ function
    yield my_class(result)
    <do some other stuff here> # This is roughly equivalent to the __exit__function

作为上下文管理器调用时效果很好,但作为直接函数调用时我不再获得 my_class 的实例。也许我只是做错了什么?

编辑 2:

请注意,我可以完全控制 my_class,包括向其添加功能的能力。从下面接受的答案中,我能够推断出我的困难源于一个基本的误解:我在想无论我叫什么(上面例子中的my_func)都需要有 __exit____enter__ 函数。这是不正确的。事实上,只有函数 returns(上例中的 my_class)需要函数才能作为上下文管理器工作。

您要 运行 遇到的困难是,对于一个既用作上下文管理器 (with foo() as x) 又用作常规函数 (x = foo()) 的函数,从函数返回的对象需要同时具有 __enter____exit__ 方法……并且在一般情况下没有很好的方法来向现有对象添加方法。

一种方法可能是创建一个包装器 class,它使用 __getattr__ 将方法和属性传递给原始对象:

class ContextWrapper(object):
    def __init__(self, obj):
        self.__obj = obj

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        ... handle __exit__ ...

    def __getattr__(self, attr):
        return getattr(self.__obj, attr)

但这会导致一些微妙的问题,因为它 与原始函数返回的对象 不完全相同(例如,isinstance 测试将失败,一些内置函数如 iter(obj) 将无法按预期工作等)。

您还可以动态地子class 返回的对象,如下所示::

class ObjectWrapper(BaseClass):
    def __init__(self, obj):
        self.__class__ = type(
            obj.__class__.__name__,
            (self.__class__, obj.__class__),
            {},
        )
        self.__dict__ = obj.__dict__

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        ... handle __exit__ ...

但这种方法也有问题(如链接 post 中所述),如果没有 strong,我个人不会愿意介绍这种神奇的级别理由。

我通常更喜欢添加明确的 __enter____exit__ 方法,或者使用像 contextlib.closing:

这样的助手
with closing(my_func()) as my_obj:
    … do stuff …

为了清楚起见:如果您能够更改 my_class,您当然会向 class 添加 __enter__/__exit__ 描述符。

如果您无法更改 my_class(这是我从您的问题中推断出来的),这就是我所指的解决方案:

class my_class(object):

    def __init__(self, result):
        print("this works", result)

class manage_me(object):

    def __init__(self, callback):
        self.callback = callback

    def __enter__(self):
        return self

    def __exit__(self, ex_typ, ex_val, traceback):
        return True

    def __call__(self, *args, **kwargs):
        return self.callback(*args, **kwargs)


def my_func(arg_1,arg_2):
    result=arg_1+arg_2
    return my_class(result)


my_func_object = manage_me(my_func) 

my_func_object(1, 1)
with my_func_object as mf:
    mf(1, 2)

作为装饰者:

@manage_me
def my_decorated_func(arg_1, arg_2):
    result = arg_1 + arg_2
    return my_class(result)

my_decorated_func(1, 3)
with my_decorated_func as mf:
    mf(1, 4)