【问题标题】:Multiple ways to invoke context manager in python在 python 中调用上下文管理器的多种方法
【发布时间】:2018-11-29 20:12:02
【问题描述】:

背景

我在 python 中有一个类,它接收一个互斥体列表。然后它对该列表进行排序,并使用__enter__()__exit__() 按特定顺序锁定/解锁所有互斥锁以防止死锁。

该类目前为我们省去了很多潜在死锁的麻烦,因为我们可以在 RAII style 中调用它,即:

self.lock = SuperLock(list_of_locks)
# Lock all mutexes.
with self.lock:
    # Issue calls to all hardware protected by these locks.

问题

我们想为这个类公开一些方法来提供一个 RAII 风格的 API,这样当以某种方式调用时,我们一次只能锁定一半的互斥锁,即:

self.lock = SuperLock(list_of_locks)
# Lock all mutexes.
with self.lock:
    # Issue calls to all hardware protected by these locks.

# Lock the first half of the mutexes in SuperLock.list_of_locks
with self.lock.first_half_only:
    # Issue calls to all hardware protected by these locks.

# Lock the second half of the mutexes in SuperLock.list_of_locks
with self.lock.second_half_only:
    # Issue calls to all hardware protected by these locks.

问题

有没有办法提供这种类型的功能,以便我可以调用 with self.lock.first_half_onlywith self.lock_first_half_only() 为用户提供简单的 API?我们希望将所有这些功能保留在一个类中。

谢谢。

【问题讨论】:

    标签: python python-2.7 class python-2.x contextmanager


    【解决方案1】:

    是的,你可以得到这个接口。将在 with 语句的上下文中进入/退出的对象是已解析的属性。因此,您可以继续将上下文管理器定义为上下文管理器的属性:

    from contextlib import ExitStack  # pip install contextlib2
    from contextlib import contextmanager
    
    @contextmanager
    def lock(name):
        print("entering lock {}".format(name))
        yield
        print("exiting lock {}".format(name))
    
    @contextmanager
    def many(contexts):
        with ExitStack() as stack:
            for cm in contexts:
                stack.enter_context(cm)
            yield
    
    class SuperLock(object):
    
        def __init__(self, list_of_locks):
            self.list_of_locks = list_of_locks
    
        def __enter__(self):
            # implement for entering the `with self.lock:` use case
            return self
    
        def __exit__(self, exce_type, exc_value, traceback):
            pass
    
        @property
        def first_half_only(self):
            return many(self.list_of_locks[:4])
    
        @property
        def second_half_only(self):
            # yo dawg, we herd you like with-statements
            return many(self.list_of_locks[4:])
    

    当您创建并返回一个新的上下文管理器时,您可以使用实例中的状态(即self)。

    示例用法:

    >>> list_of_locks = [lock(i) for i in range(8)] 
    >>> super_lock = SuperLock(list_of_locks) 
    >>> with super_lock.first_half_only: 
    ...     print('indented') 
    ...   
    entering lock 0
    entering lock 1
    entering lock 2
    entering lock 3
    indented
    exiting lock 3
    exiting lock 2
    exiting lock 1
    exiting lock 0
    

    编辑:上面显示的lock 生成器上下文管理器的基于类的等效项

    class lock(object):
    
        def __init__(self, name):
            self.name = name
    
        def __enter__(self):
            print("entering lock {}".format(self.name))
            return self
    
        def __exit__(self, exce_type, exc_value, traceback):
            print("exiting lock {}".format(self.name))
            # If you want to handle the exception (if any), you may use the
            # return value of this method to suppress re-raising error on exit
    

    【讨论】:

    • 我可以请求有关此答案的更多信息,也许是实例化/使用它的简短示例? lockyield 调用会去哪里?另外,是否需要我打电话给inst.first_half_onlyinst.first_half_only()?谢谢。
    • 另外,我可以举一个“创建并返回一个新的上下文管理器”的例子吗?
    • 我添加了更多关于此的上下文,请原谅双关语。编写的当前代码的接口将是with inst.first_half_only,因为它最初是在问题中指定的。如果像 with inst.first_half_only() 这样的接口是首选的,例如传递额外的参数,那么你可以删除 @property 装饰器。
    • __enter__()__exit()__ 方法总是有效,但调用 return many() 仅第一次有效,有什么特别(明显)的原因吗?
    • 如果我尝试使用上下文两次,示例将失败并显示RuntimeError: generator didn't yield.
    【解决方案2】:
    from contextlib import contextmanager
    
    class A:
    
        @contextmanager
        def i_am_lock(self):
            print("entering")
            yield
            print("leaving")
    
    a = A()
    
    with a.i_am_lock():
        print("inside")
    

    输出:

    entering
    inside
    leaving
    

    您还可以使用contextlib.ExitStack 更好地管理您的锁。

    【讨论】:

      【解决方案3】:

      我会使用SimpleNamespace 来允许属性访问不同的SuperLock 对象,例如:

      from types import SimpleNamespace
      
      self.lock = SimpleNamespace(
          all=SuperLock(list_of_locks),
          first_two_locks=SuperLock(list_of_locks[:2]),
          other_locks=SuperLock(list_of_locks[2:])
      )
      
      with self.lock.all:
          # Issue calls to all hardware protected by these locks.
      
      with self.lock.first_two_locks:
          # Issue calls to all hardware protected by these locks.
      
      with self.lock.other_locks:
          # Issue calls to all hardware protected by these locks.
      

      编辑:

      对于python 2,你可以使用这个类来实现类似的行为:

      class SimpleNamespace:
          def __init__(self, **kwargs):
              self.__dict__.update(kwargs)
      

      【讨论】:

      • 此类型在python-2.x中不可用。
      • 没错,但是这个类可以用三行代码轻松创建。我更新了我的答案。另一种选择当然是创建一个真正的类
      • 只是想把它作为你的解决方案的替代品(我确实喜欢)。我认为这真的取决于你是否需要在代码库的不同部分使用不同的锁,或者它是否总是相同的。在第一种情况下,我希望我的解决方案更好,而在后一种情况下,我更喜欢你的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-21
      • 1970-01-01
      相关资源
      最近更新 更多