![](/img/trans.png)
[英]should the return value for `__enter__` method always be `self` in python
[英]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
,並調用FileReader
的close()
自身的內部方法close()
方法。 但是,如果這是 Python,並且FileReader
將在其__enter__
方法中返回 self 以外的對象,這將使這種安排變得更加復雜。 BufferedReader
的作者必須解決以下問題:
FileReader
用於我自己的方法時,我是直接使用FileReader
還是其__enter__
方法返回的對象? 返回的對象甚至支持哪些方法?__exit__
方法中,我是否只需要關閉FileReader
對象,或者__enter__
方法中返回的對象?__enter__
實際上在調用時返回了一個不同的對象,會發生什么? 我現在是否需要保留它返回的所有不同對象的集合,以防有人對我多次調用__enter__
? 當我需要使用這些對象時,我如何知道使用哪一個? 而這樣的例子不勝枚舉。 所有這些問題的一個半成功的解決方案是簡單地避免在一個上下文管理器類之后清理另一個上下文管理器類。 在我的示例中,這意味着我們需要兩個嵌套with
塊 - 一個用於FileReader
,另一個用於BufferedReader
。 然而,這讓我們編寫了更多的樣板代碼,而且看起來不那么優雅。
總而言之,這些問題讓我相信,雖然 Python 確實允許我們在__enter__
方法中返回self
以外的東西,但這種行為應該簡單地避免。 關於這些問題,有沒有官方或半官方的說法? 負責任的 Python 開發人員應該如何編寫解決這些問題的代碼?
TLDR:從__enter__
返回self
以外的東西是非常好的,而且是不錯的做法。
引入 PEP 343和上下文管理器規范明確將此列為所需用例。
返回相關對象的上下文管理器的一個示例是
decimal.localcontext()
返回的decimal.localcontext()
。 這些管理器將活動的十進制上下文設置為原始十進制上下文的副本,然后返回該副本。 這允許對with
語句主體中的當前十進制上下文進行更改,而不會影響with
語句之外的代碼。
標准庫有幾個從__enter__
返回self
以外的東西的例子。 值得注意的是, contextlib
大部分內容都符合這種模式。
contextlib.contextmanager
產生不能返回self
上下文管理器,因為沒有這樣的東西。contextlib.closing
包裝一個thing
並在__enter__
上返回它。contextlib.nullcontext
返回一個預定義的常量threading.Lock
返回一個布爾值decimal.localcontext
返回其參數的副本上下文管理器協議明確了上下文管理器是什么,誰負責清理。 最重要的是, __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.