首页 文章

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

提问于
浏览
10

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

my_file=open(filename,'w')

要么

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

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

我已经能够弄清楚如何编写上下文管理器,或者通过创建一个带有 __enter____exit__ 函数的类,或者通过在函数上使用 @contextlib.contextmanager 装饰器而不是 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 回答

  • 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

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

相关问题