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

时间:2016-02-11 21:59:12

标签: python contextmanager

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

my_file=open(filename,'w')

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

两者都为您提供了一个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)

因此该函数接受了许多参数,并对它们进行了操作,并使用该东西的结果来初始化一个类,然后返回它。最终结果是我有一个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

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

编辑2:

请注意,我可以完全控制my_class,包括向其添加功能的功能。从下面接受的答案中,我能够推断出我的困难源于一个基本的误解:我在想我所谓的(上面的例子中my_func)需要__exit__和{{ 1}}功能。这是不正确的。实际上,它只是返回(上例中的__enter__)的函数,它需要函数才能作为上下文管理器。

2 个答案:

答案 0 :(得分:1)

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

一种方法可能是创建一个使用__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)一样按预期工作,等等。)

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

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__ ...

但是这种方法也存在问题(如链接帖子中所述),而且如果没有强大的理由,我个人不会感到很舒服。

我通常更喜欢添加明确的__enter____exit__方法,或使用contextlib.closing之类的帮助:

with closing(my_func()) as my_obj:
    … do stuff …

答案 1 :(得分:1)

为了清楚起见:如果您能够更改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)

作为装饰者:

@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)