2016-02-11 57 views
9

在python中,有很多函数既可以作为标准函数又可以作为上下文管理器。例如open()可以被称为无论是作为:Python:标准函数和上下文管理器?

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____enter__函数。这是不正确的。实际上,它只是功能返回my_class在上面的示例中)需要函数才能用作上下文管理器。

+2

'open'是一个返回类实例的函数。无论你使用'myfile = open(filename)'还是'以open(filename)作为myfile',它仍然是同一类的一个实例。没有什么改变。 – zondo

+0

@zondo真的,我正在努力写的功能。但是,当我将函数包装在'@ contextlib.contextmanager'装饰器中,并将其作为标准函数调用时,我返回的类不是函数中的类“yield”。只有当我将其称为上下文管理器时,我才能获得该类。我会添加一个简单的例子。 – ibrewster

+1

在你的课堂上定义一个'__call__'方法。 –

回答

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 … 
+0

啊,我错过的关键是对象*返回*需要'__enter__'和'__exit__'函数 - 不一定是对象(在本例中为函数)*调用*。因此,通过将这些函数添加到函数返回的类中,使用'__enter__'函数简单地返回'self',它就可以工作! – ibrewster

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) 
+1

我很能够改变my_class。我的困惑源于这样的事实:我没有直接调用(或实例化)my_class,而是调用返回my_class实例的函数,我希望能够将该函数用作函数或上下文经理 - 就像你可以用open()函数一样。我没有意识到它是需要作为上下文管理器运行的*返回*对象 - 我认为我需要作为上下文管理器的函数。 – ibrewster