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