在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__
)的函数,它需要函数才能作为上下文管理器。
答案 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)