簡體   English   中英

在 `__enter__` 中返回除 `self` 以外的值是一種反模式嗎?

[英]Is returning a value other than `self` in `__enter__` an anti-pattern?

此相關的問題,而總是有一種獨特的方式使用語言功能的一些圖書館的例子,我想知道是否返回的值不是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的作者必須解決以下問題:

  1. 當我需要將FileReader用於我自己的方法時,我是直接使用FileReader還是其__enter__方法返回的對象? 返回的對象甚至支持哪些方法?
  2. 在我的__exit__方法中,我是否只需要關閉FileReader對象,或者__enter__方法中返回的對象?
  3. 如果__enter__實際上在調用時返回了一個不同的對象,會發生什么? 我現在是否需要保留它返回的所有不同對象的集合,以防有人對我多次調用__enter__ 當我需要使用這些對象時,我如何知道使用哪一個?

而這樣的例子不勝枚舉。 所有這些問題的一個半成功的解決方案是簡單地避免在一個上下文管理器類之后清理另一個上下文管理器類。 在我的示例中,這意味着我們需要兩個嵌套with塊 - 一個用於FileReader ,另一個用於BufferedReader 然而,這讓我們編寫了更多的樣板代碼,而且看起來不那么優雅。

總而言之,這些問題讓我相信,雖然 Python 確實允許我們在__enter__方法中返回self以外的東西,但這種行為應該簡單地避免。 關於這些問題,有沒有官方或半官方的說法? 負責任的 Python 開發人員應該如何編寫解決這些問題的代碼?

TLDR:從__enter__返回self以外的東西是非常好的,而且是不錯的做法。

引入 PEP 343上下文管理器規范明確將此列為所需用例。

返回相關對象的上下文管理器的一個示例是decimal.localcontext()返回的decimal.localcontext() 這些管理器將活動的十進制上下文設置為原始十進制上下文的副本,然后返回該副本。 這允許對with語句主體中的當前十進制上下文進行更改,而不會影響with語句之外的代碼。


標准庫有幾個從__enter__返回self以外的東西的例子。 值得注意的是, contextlib大部分內容都符合這種模式。


上下文管理器協議明確了上下文管理器是什么,誰負責清理。 最重要的是, __enter__的返回值對於協議來說無關緊要

該協議的粗略解釋是:當某個東西運行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__ 如果外部上下文管理器已經有進入的內部上下文管理器,則不負責清理。


在某些情況下,從__enter__返回self實際上是不好的做法。 __enter__返回self應該只在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__

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM