gpt4 book ai didi

python - 在 `self` 中返回 `__enter__` 以外的值是反模式吗?

转载 作者:行者123 更新时间:2023-12-01 12:02:28 25 4
gpt4 key购买 nike

关注 this related question ,虽然总是有一些库以独特的方式使用语言特性的例子,但我想知道是否返回 self 以外的值在 __enter__方法应该被认为是一种反模式。

这在我看来是个坏主意的主要原因是它使包装上下文管理器成为问题。例如,在 Java 中(也可能在 C# 中),可以包装一个 AutoCloseable另一个类中的类,它将在内部类之后进行清理,例如以下代码片段:

try (BufferedReader reader = 
new BufferedReader(new FileReader("src/main/resources/input.txt"))) {
return readAllLines(reader);
}

在这里, BufferedReader包裹 FileReader , 并调用 FileReaderclose()自己的方法 close()方法。但是,如果这是 Python,并且 FileReader会在其 __enter__ 中返回除 self 以外的对象方法,这将使这样的安排更加复杂。 BufferedReader 的作者必须解决以下问题:
  • 当我需要使用 FileReader对于我自己的方法,我是否使用 FileReader直接或其__enter__返回的对象方法?返回的对象甚至支持哪些方法?
  • 在我的 __exit__方法,我是否只需要关闭 FileReader对象,或 __enter__ 中返回的对象方法?
  • 如果 __enter__ 会发生什么?实际上在调用时返回了一个不同的对象?我现在是否需要保留它返回的所有不同对象的集合,以防有人调用 __enter__几次对我?当我需要使用这些对象时,我如何知道使用哪一个?

  • 而这样的例子不胜枚举。所有这些问题的一个半成功的解决方案是简单地避免在另一个上下文管理器类之后清理一个上下文管理器类。在我的示例中,这意味着我们需要两个嵌套的 with block - 一个用于 FileReader , 一个用于 BufferedReader .然而,这使我们编写了更多样板代码,并且看起来明显不那么优雅。

    总而言之,这些问题让我相信虽然 Python 确实允许我们返回 self 以外的东西。在 __enter__方法,应该简单地避免这种行为。关于这些问题是否有一些官方或半官方的评论?一个负责任的 Python 开发人员应该如何编写代码来解决这些问题?

    最佳答案

    TLDR:返回 self 以外的其他内容来自 __enter__非常好,也不错。

    introducing PEP 343Context Manager specification明确将此列为所需的用例。

    An example of a context manager that returns a related object is the one returned by decimal.localcontext(). These managers set the active decimal context to a copy of the original decimal context and then return the copy. This allows changes to be made to the current decimal context in the body of the with statement without affecting code outside the with statement.



    标准库有几个返回 self 以外的内容的示例。来自 __enter__ .值得注意的是, contextlib 的大部分内容匹配这个模式。
  • contextlib.contextmanager 生成无法返回 self 的上下文管理器,因为没有这样的东西。
  • contextlib.closing 包裹 thing并返回 __enter__ .
  • contextlib.nullcontext 返回一个预定义的常量
  • threading.Lock 返回 bool 值
  • decimal.localcontext 返回其参数的副本


  • 上下文管理器协议(protocol)明确了什么是上下文管理器,以及谁负责清理。最重要的是, __enter__ 的返回值对协议(protocol)无关紧要 .

    协议(protocol)的粗略解释是: 当某事运行时cm.__enter__ ,它负责运行cm.__exit__ . 值得注意的是,任何代码都可以访问 cm (上下文管理器本身); cm.__enter__ 的结果无需调用 cm.__exit__ .

    换句话说,采用(并运行) ContextManager 的代码必须完全运行它。任何其他代码都不必关心它的值是否来自 ContextManager或不。
    # entering a context manager requires closing it…
    def managing(cm: ContextManager):
    value = cm.__enter__() # must clean up `cm` after this point
    try:
    yield from unmanaged(value)
    except BaseException as exc:
    if not cm.__exit__(type(exc), exc, exc.__traceback__):
    raise
    else:
    cm.__exit__(None, None, None)

    # …other code does not need to know where its values come from
    def unmanaged(smth: Any):
    yield smth

    当上下文管理器包装其他的时,同样的规则适用:如果外部上下文管理器调用内部的 __enter__ ,它必须调用它的 __exit__也是。如果外部上下文管理器已经有进入的内部上下文管理器,它不负责清理。

    在某些情况下,返回 self 实际上是不好的做法。来自 __enter__ .返回 self来自 __enter__只有在 self 时才应该这样做预先完全初始化;如果 __enter__运行任何初始化代码,应返回一个单独的对象。
    class BadContextManager:
    """
    Anti Pattern: Context manager is in inconsistent state before ``__enter__``
    """
    def __init__(self, path):
    self.path = path
    self._file = None # BAD: initialisation not complete

    def read(self, n: int):
    return self._file.read(n) # fails before the context is entered!

    def __enter__(self) -> 'BadContextManager':
    self._file = open(self.path)
    return self # BAD: self was not valid before

    def __exit__(self, exc_type, exc_val, tb):
    self._file.close()

    class GoodContext:
    def __init__(self, path):
    self.path = path
    self._file = None # GOOD: Inconsistent state not visible/used

    def __enter__(self) -> TextIO:
    if self._file is not None:
    raise RuntimeError(f'{self.__class__.__name__} is not re-entrant')
    self._file = open(self.path)
    return self._file # GOOD: value was not accessible before

    def __exit__(self, exc_type, exc_val, tb):
    self._file.close()

    值得注意的是,即使 GoodContext返回一个不同的对象,它仍然负责清理。另一个上下文管理器包装 GoodContext不需要关闭返回值,只需要调用 GoodContext.__exit__ .

    关于python - 在 `self` 中返回 `__enter__` 以外的值是反模式吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60561959/

    25 4 0
    文章推荐: java - List 与 List 不同时的示例
    文章推荐: Java 重命名不起作用