ContextDecorator documentation is unclear.
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 77.2k
- フォーク
- 35.9k
- PR マージ指標
- PR 指標を取得中
説明
Bug report
Bug description:
Overview:
ContextDecorator can not be safely used on functions that make recursive calls, or may be used with multithreading, even if those context managers support use in sequential with statements. The documentation suggests otherwise, and could be clarified. Additionally, this functionality could be added via a class method.
Detailed description
The documentation for ContextDecorator states:
This change is just syntactic sugar for any construct of the following form:
def f(): with cm(): # Do stuffContextDecorator lets you instead write:
@cm() def f(): # Do stuff
However, ContextDecorator is closer in functionality to the following:
cm = cm()
def f():
with cm:
# Do stuff
The documentation does contain the following warning. However, the wording could be more clear, especially given the syntactic sugar example.
Note As the decorated function must be able to be called multiple times, the underlying context manager must support use in multiple with statements. If this is not the case, then the original construct with the explicit with statement inside the function should be used.
Repro Code
This code demonstrates the issue:
import contextlib
import time
class timed(contextlib.ContextDecorator):
def __enter__(self):
self._start_time = time.monotonic()
def __exit__(self, *exc):
print(f"Execution took {time.monotonic() - self._start_time:.0f} seconds")
@timed()
def my_func(recurse_once = False):
time.sleep(1)
if recurse_once:
my_func()
my_func(recurse_once = True)
Expected output:
Execution took 1 seconds
Execution took 2 seconds
Actual output:
Execution took 1 seconds
Execution took 1 seconds
Potential fixes
Implementing one or more of these fixes could alleviate the issue.
1. Clarify the syntactic sugar section to show that all function invocations share a single instance of CM.
The "syntactic sugar" section could be changed to read:
ContextDecorator lets you instead write:
@cm() def f(): # Do stuffWhich is equivalent to:
cm = cm() def f(): with cm: # Do stuff
2. Clarify the warning note
The warning note could be changed to make it more clear that separate functions share state in the context manager.
Note The underlying context manager is instantiated once when the function definition is evaluated: this instance is shared between all calls to the function. As the decorated function must be able to be called multiple times, the underlying context manager must support use in multiple with statements. If this is not the case, then the original construct with the explicit with statement inside the function should be used.
3. Provide a method for wrapping functions with stateful context managers
For example:
def wrap_with_context(cm_factory, *cm_args, **cm_kwargs):
"""Wraps the decorated function with a context created by cm_factory."""
def wrap(func):
@functools.wraps(func)
def inner(*func_args, **func_kwargs):
with cm_factory(*cm_args, **cm_kwargs):
return func(*func_args, **func_kwargs)
return inner
return wrap
The previous example now works as expected:
@wrap_with_context(timed)
def my_func2(recurse_once = False):
time.sleep(1)
if recurse_once:
my_func2()
my_func2(recurse_once = True)
Actual output:
Execution took 1 seconds
Execution took 2 seconds
4. Add class method to ContextDecorator that acts as a decorator and a factory.
class ContextDecorator:
...
@classmethod
def wrap(cls, *cls_args, **cls_kwargs):
def enclose(func):
@functools.wraps(func)
def inner(*args, **kwds):
with cls(*cls_args, **cls_kwargs):
return func(*args, **kwds)
return inner
return enclose
This allows usage that is similar to existing, but instantiates a separate CM for each function invocation:
@timed.wrap()
def my_func2(recurse_once = False):
time.sleep(1)
if recurse_once:
my_func2()
CPython versions tested on:
3.13
Operating systems tested on:
macOS
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
リンク先の Python ドキュメントの ContextDecorator セクションから始め、特にシンタックスシュガーの例と警告の注記を確認してください。基盤となる 1 つのコンテキストマネージャーインスタンスが関数呼び出し間で共有されることを明確にし、明示的な with 文が必要になる場合を説明してください。改訂されたドキュメントでは、再帰的な使用と並行使用を正確に記述する必要があります。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- documentation
- issue の種類
- ドキュメント
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 45/100