在Python中充当装饰器和上下文管理器的函数?

时间:2012-02-09 15:22:20

标签: python decorator contextmanager

这可能会把事情推得太远,但主要是出于好奇......

是否可以让一个可调用的对象(函数/类)同时充当 一个上下文管理器和一个装饰器:

def xxx(*args, **kw):
    # or as a class

@xxx(foo, bar)
def im_decorated(a, b):
    print('do the stuff')

with xxx(foo, bar):
    print('do the stuff')

5 个答案:

答案 0 :(得分:41)

从Python 3.2开始,对此的支持甚至包含在标准库中。从类contextlib.ContextDecorator派生,可以很容易地编写可以用作装饰器或上下文管理器的类。此功能可以轻松地向后移植到Python 2.x - 这是一个基本实现:

class ContextDecorator(object):
    def __call__(self, f):
        @functools.wraps(f)
        def decorated(*args, **kwds):
            with self:
                return f(*args, **kwds)
        return decorated

从此课程中派生您的上下文管理员,并照常定义__enter__()__exit__()方法。

答案 1 :(得分:21)

在Python 3.2+中,您可以使用@contextlib.contextmanager定义一个也是装饰器的上下文管理器。

来自文档:

  

contextmanager()使用ContextDecorator,因此它创建的上下文管理器可用作装饰器以及with语句

使用示例:

type
  TClientData = record
    UserName: string;
  end;

...

// during login...
var
  ClientData: TClientData;
begin
  New(ClientData);
  ClientData.UserName := ...; // read from the client
  Socket.Data := ClientData;
end;

...

// during logout/disconnect...
var
  ClientData: TClientData;
begin
  ClientData := Socket.Data;
  Socket.Data := nil;
  Dispose(ClientData);
end;

...

// during private messaging
var
  client: TCustomWinSocket;
begin
  for I := 0 to ServerSocket1.Socket.ActiveConnections-1 do
  begin
    client := ServerSocket1.Socket.Connections[i];
    if TClientData(client.Data).UserName = msgUser then
    begin
      client.SendText(msg);
      break;
    end;
  end;
end;

答案 2 :(得分:12)

class Decontext(object):
    """
    makes a context manager also act as decorator
    """
    def __init__(self, context_manager):
        self._cm = context_manager
    def __enter__(self):
        return self._cm.__enter__()
    def __exit__(self, *args, **kwds):
        return self._cm.__exit__(*args, **kwds)
    def __call__(self, func):
        def wrapper(*args, **kwds):
            with self:
                return func(*args, **kwds)
        return wrapper

现在你可以这样做:

mydeco = Decontext(some_context_manager)

并允许两者

@mydeco
def foo(...):
    do_bar()

foo(...)

with mydeco:
    do_bar()

答案 3 :(得分:3)

以下是一个例子:

class ContextDecorator(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
        print "init", foo, bar

    def __call__(self, f):
        print "call"
        def wrapped_f():
            print "about to call"
            f()
            print "done calling"
        return wrapped_f

    def __enter__(self):
        print "enter"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print "exit"

with ContextDecorator(1, 2):
    print "with"

@ContextDecorator(3, 4)
def sample():
    print "sample"

sample()

打印:

init 1 2
enter
with
exit
init 3 4
call
about to call
sample
done calling

答案 4 :(得分:1)

尽管我在这里同意(并赞成)@jterrace,但我添加了一个非常细微的变体,该变体返回装饰的函数,并包括装饰器和装饰的函数的参数。

class Decon:
    def __init__(self, a=None, b=None, c=True):
        self.a = a
        self.b = b
        self.c = c

    def __enter__(self):
        # only need to return self 
        # if you want access to it
        # inside the context
        return self 

    def __exit__(self, exit_type, exit_value, exit_traceback):
        # clean up anything you need to
        # otherwise, nothing much more here
        pass

    def __call__(self, func):
        def decorator(*args, **kwargs):
            with self:
                return func(*args, **kwargs)
        return decorator