Python上下文管理器到装饰器(及其反向)

2024-09-28 17:31:01 发布

您现在位置:Python中文网/ 问答频道 /正文

我想要:

# Simple example, one could replace try/except by any other nested construct
def mycontextmanager_generator(foo):
    try:
        yield
    except:
        print 'bar'
        raise

mycontextmanager = build_contextmanager(mycontextmanager_generator)
mydecorator = build_decorator(mycontextmanager_generator)


>>> with mycontextmanager():
>>>     raise Exception('baz gone bar in context manager')
... bar


>>> @mydecorator()
>>> def bazzer():
>>>     raise Exception('baz gone bar in decorator')
>>> bazzer()
... bar

在这个例子中,我从一个生成器函数构建了一个上下文管理器,从同一个函数构建了一个decorator。这是我试图以不成功的方式实现的。在

更一般地说,我想要的是干的:写一次try/except块,然后通过decorator上下文管理器重用它一次:无论是在生成器函数中还是在任何其他包装器中,只编写一次try/except bloc。在

ContextDecorator东西(py3/contextlib2中的contextlib中)只能用于类,但在这种情况下它似乎没有用。。。我错过什么了吗?有没有一种方法可以使用__enter____exit__使用基于类的ContextManager实现try/except块吗?在

或者,是否有可能将使用yield语法构建的contextmanager转换为decorator?在

或者相反(decorator到contextmanager)?在

如果没有,很乐意知道Python在这方面的局限性是什么。在

据我所知,yield语法与Python解释器和上下文切换非常紧密地绑定在一起,我不知道是否可以在这一点上改变它的行为。在


Tags: 函数builddefexceptionbardecoratorbazgenerator
2条回答

通过组合contextmanager用于管理其上下文的类(_GeneratorContextManager)和ContextDecorator类,可以轻松地实现所需的功能。例如

from contextlib import ContextDecorator, _GeneratorContextManager
from functools import wraps

class MyContextManager(_GeneratorContextManager, ContextDecorator):
    pass

def contextmanager(func):
    @wraps(func)
    def helper(*args, **kwds):
        return MyContextManager(func, args, kwds)
    return helper

@contextmanager
def print_bar_on_error():
    try:
        yield
    except:
        print('bar')
        raise

with print_bar_on_error():
    raise Exception('baz gone bar in context manager')

产生:

^{pr2}$

当装饰用的时候

@print_bar_on_error()
def bazzer():
    raise Exception('baz gone bar in decorator')
bazzer()

产生:

bar
Traceback (most recent call last):
  File "run.py", line 32, in <module>
    bazzer()
  File "c:\Users\User\AppData\Local\Programs\Python\Python35-32\lib\contextlib.py", line 30, in inner
    return func(*args, **kwds)
  File "run.py", line 31, in bazzer
    raise Exception('baz gone bar in decorator')
Exception: baz gone bar in decorator
    return func(*args, **kwds)
Exception: baz gone bar in decorator

一个比Dunes的更容易理解的解决方案,尽管没有利用ContextDecorator双语法。在

import contextlib
import functools

def handler():
    try:
        yield
    except:
        print 'bar'


my_contextmanager = contextlib.contextmanager(handler)


def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with my_contextmanager():
            func(*args, **kwargs)
    return wrapper


with my_contextmanager():
    raise Exception('baz')

@my_decorator
def f():
    raise Exception('baz')

f()

给出:

^{pr2}$

相关问题 更多 >