Python 在覆盖方法中访问父 class 的 "with" 语句

Python accessing parent class's "with" statement in an overridden method

我有一个基础 class,其方法使用 with 语句。在子 class 中,我覆盖了相同的方法,然后想访问相同的 with 语句(而不是有两个 with 语句)。

解决这个问题的标准方法是什么?

有关示例和可能的解决方案,请参见下文。


示例 使用 threading.Lock

from threading import Lock


class BaseClass:
    def __init__(self):
        self.lock = Lock()
        self._data = 0

    def do_something_locked(self) -> None:
        with self.lock:
            self._data += 5


class ChildClass(BaseClass):
    def do_something_locked(self) -> None:
        super().do_something_locked()
        # Obviously the parent class's self.lock's __exit__ method has 
        # already been called.  What are accepted methods to add more 
        # functionality inside parent class's "with" statement?
        with self.lock:
            self._data += 1

可能的解决方案

我的第一个想法是在 BaseClass 中定义一个私有方法,如下所示:

    def do_something_locked(self) -> None:
        with self.lock:
            self._do_something()

    def _do_something(self) -> None:
        self._data += 5

然后 ChildClass 可以覆盖 _do_something。这会很好用。

我想知道,是否还有其他常见的解决此问题的模式?

使用 re-entrant 锁。这将自动“连接”嵌套的 with 语句,仅在 outer-most with.

之后释放锁
from threading import RLock


class BaseClass:
    def __init__(self):
        self.lock = RLock()
        self._data = 0

    def do_something_locked(self) -> None:
        with self.lock:
            self._data += 5


class ChildClass(BaseClass):
    def do_something_locked(self) -> None:
        with self.lock:
            super().do_something_locked()
            self._data += 1

一般来说,reentrant context managers 的模式明确存在以允许 possibly-nested 上下文。

These context managers can not only be used in multiple with statements, but may also be used inside a with statement that is already using the same context manager.

My first inclination is to define a private method in the BaseClass like so... And then the ChildClass can just override _do_something. This will work fine.

这是解决问题的好方法,even when you don't have a special requirement(比如需要保留在 with 块上下文中)。我不会为“hook”方法名称使用前导下划线,因为您期望在派生 classes 中覆盖的任何内容在逻辑上都是 class 接口的一部分。此外,如果 self._data += 5 部分始终需要发生,则将其保留在 do_something_locked.

are there any other common patterns of solving this problem?

具体问题,您可以使用 re-entrant 锁,如其他答案所示。您也可以忽略 classes 相关的事实,并使用依赖注入 - 在基础 class 中创建一个通用方法,该方法接受可调用并执行它,使用锁:

# in base class
def do_locked(self, what, *args, **kwargs):
    with self.lock:
        what(*args, **kwargs)

# in derived class
def _implementation(self):
    pass
def do_interesting_thing(self):
    # pass in our own bound method, which takes no arguments
    self._do_locked(self._implementation)

这种方式允许客户端代码以自定义方式使用锁。如果您不需要或不想要该功能,这可能不是一个好主意。