简体   繁体   English

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

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

Following this related question , while there are always examples of some library using a language feature in a unique way, I was wondering whether returning a value other than self in an __enter__ method should be considered an anti-pattern.此相关的问题,而总是有一种独特的方式使用语言功能的一些图书馆的例子,我想知道是否返回的值不是self__enter__方法应被视为一个反模式。

The main reason why this seems to me like a bad idea is that it makes wrapping context managers problematic.在我看来这不是个好主意的主要原因是它使包装上下文管理器有问题。 For example, in Java (also possible in C#), one can wrap an AutoCloseable class in another class which will take care of cleaning up after the inner class, like in the following code snippet:例如,在 Java 中(也可以在 C# 中),可以将AutoCloseable类包装在另一个类中,该类将在内部类之后进行清理,如下面的代码片段所示:

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

Here, BufferedReader wraps FileReader , and calls FileReader 's close() method inside its own close() method.在这里, BufferedReader包装FileReader ,并调用FileReaderclose()自身的内部方法close()方法。 However, if this was Python, and FileReader would've returned an object other than self in its __enter__ method, this would make such an arrangement significantly more complicated.但是,如果这是 Python,并且FileReader将在其__enter__方法中返回 self 以外的对象,这将使这种安排变得更加复杂。 The following issues would have to be addressed by the writer of BufferedReader : BufferedReader的作者必须解决以下问题:

  1. When I need to use FileReader for my own methods, do I use FileReader directly or the object returned by its __enter__ method?当我需要将FileReader用于我自己的方法时,我是直接使用FileReader还是其__enter__方法返回的对象? What methods are even supported by the returned object?返回的对象甚至支持哪些方法?
  2. In my __exit__ method, do I need to close only the FileReader object, or the object returned in the __enter__ method?在我的__exit__方法中,我是否只需要关闭FileReader对象,或者__enter__方法中返回的对象?
  3. What happens if __enter__ actually returns a different object on its call?如果__enter__实际上在调用时返回了一个不同的对象,会发生什么? Do I now need to keep a collection of all of the different objects returned by it in case someone calls __enter__ several times on me?我现在是否需要保留它返回的所有不同对象的集合,以防有人对我多次调用__enter__ How do I know which one to use when I need to use on of these objects?当我需要使用这些对象时,我如何知道使用哪一个?

And the list goes on.而这样的例子不胜枚举。 One semi-successful solution to all of these problems would be to simply avoid having one context manager class clean up after another context manager class.所有这些问题的一个半成功的解决方案是简单地避免在一个上下文管理器类之后清理另一个上下文管理器类。 In my example, that would mean that we would need two nested with blocks - one for the FileReader , and one for the BufferedReader .在我的示例中,这意味着我们需要两个嵌套with块 - 一个用于FileReader ,另一个用于BufferedReader However, this makes us write more boilerplate code, and seems significantly less elegant.然而,这让我们编写了更多的样板代码,而且看起来不那么优雅。

All in all, these issues lead me to believe that while Python does allow us to return something other than self in the __enter__ method, this behavior should simply be avoided.总而言之,这些问题让我相信,虽然 Python 确实允许我们在__enter__方法中返回self以外的东西,但这种行为应该简单地避免。 Is there some official or semi-official remarks about these issues?关于这些问题,有没有官方或半官方的说法? How should a responsible Python developer write code that addresses these issues?负责任的 Python 开发人员应该如何编写解决这些问题的代码?

TLDR: Returning something other than self from __enter__ is perfectly fine and not bad practice. TLDR:从__enter__返回self以外的东西是非常好的,而且是不错的做法。

The introducing PEP 343 and Context Manager specification expressly list this as desired use cases.引入 PEP 343上下文管理器规范明确将此列为所需用例。

An example of a context manager that returns a related object is the one returned by decimal.localcontext() .返回相关对象的上下文管理器的一个示例是decimal.localcontext()返回的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.这允许对with语句主体中的当前十进制上下文进行更改,而不会影响with语句之外的代码。


The standard library has several examples of returning something other than self from __enter__ .标准库有几个从__enter__返回self以外的东西的例子。 Notably, much of contextlib matches this pattern.值得注意的是, contextlib大部分内容都符合这种模式。


The context manager protocol makes it clear what is the context manager, and who is responsible for cleanup.上下文管理器协议明确了上下文管理器是什么,谁负责清理。 Most importantly, the return value of __enter__ is inconsequential for the protocol .最重要的是, __enter__的返回值对于协议来说无关紧要

A rough paraphrasing of the protocol is this: When something runs cm.__enter__ , it is responsible for running cm.__exit__ .该协议的粗略解释是:当某个东西运行cm.__enter__ ,它负责运行cm.__exit__ Notably, whatever code does that has access to cm (the context manager itself);值得注意的是,无论代码做什么,都可以访问cm (上下文管理器本身); the result of cm.__enter__ is not needed to call cm.__exit__ . cm.__enter__的结果不需要调用cm.__exit__

In other words, a code that takes (and runs) a ContextManager must run it completely.换句话说,需要(并运行)一个ContextManager的代码必须完全运行它。 Any other code does not have to care whether its value comes from a ContextManager or not.任何其他代码不必关心它的值是否来自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

When context managers wrap others, the same rules apply: If the outer context manager calls the inner one's __enter__ , it must call its __exit__ as well.当上下文管理器包装其他上下文时,同样的规则适用:如果外部上下文管理器调用内部的__enter__ ,它也必须调用它的__exit__ If the outer context manager already has the entered inner context manager, it is not responsible for cleanup.如果外部上下文管理器已经有进入的内部上下文管理器,则不负责清理。


In some cases it is in fact bad practice to return self from __enter__ .在某些情况下,从__enter__返回self实际上是不好的做法。 Returning self from __enter__ should only be done if self is fully initialised beforehand;__enter__返回self应该只在self事先完全初始化的情况下完成; if __enter__ runs any initialisation code, a separate object should be returned.如果__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()

Notably, even though GoodContext returns a different object, it is still responsible to clean up.值得注意的是,即使GoodContext返回一个不同的对象,它仍然负责清理。 Another context manager wrapping GoodContext does not need to close the return value, it just has to call GoodContext.__exit__ .另一个包装GoodContext上下文管理器不需要关闭返回值,它只需要调用GoodContext.__exit__

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果`__enter__`方法的返回值总是在python中是`self` - should the return value for `__enter__` method always be `self` in python Python中的BaseBean反模式 - BaseBean anti-pattern in Python 带有上下文管理器的生成器是反模式吗? - Are generators with context managers an anti-pattern? Python 中的嵌套集和字典是反模式吗? - Are nested sets and dictionaries anti-pattern in Python? Python 装饰器采用附加参数反模式 - Python decorator taking additional argument anti-pattern 使用回调满足依赖性的缺点是什么? 而且,这是反模式吗? - What are the drawbacks to using callbacks for satisfying dependencies? And, is this an anti-pattern? 使用 Django ModelField 选择作为字符串的枚举 — 反模式? - Enum using Django ModelField choices as string — anti-pattern? 将请求对象发送到Django中的模型方法是否是反模式? - Is it an anti-pattern to send a request object to a model method in Django? 向构造函数传递太多参数是否被视为反模式? - Is passing too many arguments to the constructor considered an anti-pattern? 为什么从同级包中导入模块被视为反模式? - Why is importing a module from a sibling package considered an anti-pattern?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM