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

ibr*_*ter 10 python contextmanager

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

my_file=open(filename,'w')
Run Code Online (Sandbox Code Playgroud)

要么

with open(filename,'w') as my_file:
Run Code Online (Sandbox Code Playgroud)

两者都给你一个my_file可以用来做任何你需要的对象.一般情况下,后者是优选的,但有时也可能想要做前者.

我已经能够弄清楚如何编写上下文管理器,或者通过创建一个带有__enter____exit__函数的类,或者通过@contextlib.contextmanager在函数上使用装饰器yield而不是return.然而,当我这样做时,我不能再直接使用该函数 - 使用装饰器,例如,我得到一个_GeneratorContextManager对象而不是想要的结果.当然,如果我把它作为一个类,我只会得到一个生成器类的实例,我假设它基本上是相同的.

那么我如何设计一个函数(或类)作为函数,返回一个对象或一个上下文管理器,返回一个_GeneratorContextManager或类似的东西?

编辑:

例如,假设我有一个类似下面的函数(这是高度简化的):

def my_func(arg_1,arg_2):
    result=arg_1+arg_2
    return my_class(result)
Run Code Online (Sandbox Code Playgroud)

所以函数需要一些参数,用它们做些什么,并使用那些东西的结果初始化一个类,然后返回它.最终结果是我有一个实例my_class,就像我有一个file对象,如果我已经调用open.如果我希望能够将此函数用作上下文管理器,我可以像这样修改它:

@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
Run Code Online (Sandbox Code Playgroud)

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

编辑2:

请注意,我确实可以完全控制my_class,包括向其添加功能的功能.从下面的接受的答案,我可以推断,我的困难来自于一个基本的误解朵朵:我在想,不管我叫(my_func在上面的例子中)有需要__exit____enter__功能.这是不正确的.实际上,它只是函数返回(my_class在上面的例子中)需要函数才能作为上下文管理器工作.

Dav*_*ver 11

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

\n\n

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

\n\n
class ContextWrapper(object):\n    def __init__(self, obj):\n        self.__obj = obj\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *exc):\n        ... handle __exit__ ...\n\n    def __getattr__(self, attr):\n        return getattr(self.__obj, attr)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

您还可以动态子类化返回的对象,如下所示: https: //stackoverflow.com/a/1445289/71522 ://stackoverflow.com/a/1445289/71522 :

\n\n
class ObjectWrapper(BaseClass):\n    def __init__(self, obj):\n        self.__class__ = type(\n            obj.__class__.__name__,\n            (self.__class__, obj.__class__),\n            {},\n        )\n        self.__dict__ = obj.__dict__\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *exc):\n        ... handle __exit__ ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

但这种方法也有问题(如链接文章中所述),如果没有强大的功能,我个人不会轻松地介绍这种神奇程度

\n\n

我通常更喜欢添加显式__enter____exit__方法,或者使用类似的帮助器contextlib.closing

\n\n
with closing(my_func()) as my_obj:\n    \xe2\x80\xa6 do stuff \xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n

  • 啊,我错过的关键是*返回*的对象需要`__enter__`和`__exit__`函数 - 不一定是对象(本例中的函数)*调用*。因此,通过将这些函数添加到函数返回的类中,并且“__enter__”函数仅返回“self”,它就可以工作了! (5认同)

小智 6

只是为了清楚起见:如果您能够更改my_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)
Run Code Online (Sandbox Code Playgroud)

作为装饰者:

@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)
Run Code Online (Sandbox Code Playgroud)

  • 我完全能够更改 my_class。我的困惑源于这样一个事实:我没有直接调用(或实例化)my_class - 相反,我调用一个返回 my_class 实例的函数,并且我希望能够将该函数用作函数或上下文manager - 就像使用 open() 函数一样。我没有意识到它是*返回的*对象需要充当上下文管理器 - 我认为我*调用*的函数需要成为上下文管理器。 (4认同)