1

I have a helper class Decontext that I am using to turn a context manager into a decorator (pyton 2.6).

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 

My contextmanager takes an argument and I am trying to figure out how to pass that argument when using this decorator ?

@contextmanager def sample_t(arg1): print "<%s>" % arg1 yield 

This is how I am using it which fails:

my_deco = Decontext(sample_t) @my_deco(arg1='example') def some_func() print 'works' 

EDIT:

I would like the Decontext class to pass all *args in the context_manager when the __call__ function executes.

Example:

decorator_example = Decontext(sample_t) // I don't want to pass in the argument here but when the decorator is created. How can I modify my class to make this enhancement 

Edit 2:

Example of what I expected

my_deco = Decontext(sample_t) @my_deco(arg1='example') def some_func() print 'works' 

Expected output:

'example' // running and passing argument to context_manager 'works' // after yield executing some_func 
5
  • Can you give an example of how you want to use this? It's not clear to me what your class is supposed to do. Commented Aug 7, 2014 at 19:59
  • It almost sounds like you just want this: docs.python.org/2/library/contextlib.html#module-contextlib Commented Aug 7, 2014 at 20:05
  • @Gerrat - If i define my context manager and just try to use it as a decorator without converting into one, i get an error object is not callable Commented Aug 7, 2014 at 20:09
  • Maybe you could show both your example function (eg your some_func), as well as exactly how you'd call this & what output you'd like. It's really unclear exactly what you're hoping for. Commented Aug 7, 2014 at 20:10
  • @Gerrat, not to sound too confusing and my poor understanding of decorators and contextmanagers but I am looking for a way to define a context manager that takes an argument and I understand I can just use it by the with statement but would like to make it a decorator instead if possible, preserving the ability to pass arguments Commented Aug 7, 2014 at 20:17

3 Answers 3

2

The issue you're having is that the _cm attribute you're setting up in your __init__ method isn't actually storing a context manager instance, but rather the the type of the context manager (or possibly a factory function that produces context manager instances). You need to call the type or factory later, to get an instance.

Try this, which should work for both context manager instances (assuming they're not also callable) and context manager types that require arguments:

class Decontext(object): """ makes a context manager also act as decorator """ def __init__(self, context_manager): self._cm = context_manager # this may be a cm factory or type, but that's OK def __enter__(self): return self._cm.__enter__() def __exit__(self, *args, **kwds): return self._cm.__exit__(*args, **kwds) def __call__(self, *cm_args, **cm_kwargs): try: self._cm = self._cm(*cm_args, **cm_kwargs) # try calling the cm like a type except TypeError: pass def decorator(func): def wrapper(*args, **kwds): with self: return func(*args, **kwds) return wrapper return decorator 

There's a fairly silly level of nesting going on in there, but it's what you need given the way you want to call the thing. Here's an example with it in action:

from contextlib import contextmanager @contextmanager def foo(arg): print("entered foo({!r})".format(arg)) yield print("exited foo({!r})".format(arg)) foo_deco_factory = Decontext(foo) @foo_deco_factory("bar") def baz(arg): print("called baz({!r})".format(arg)) baz("quux") 

It will output:

entered foo("bar") called baz("quux") exited foo("bar") 

Note that trying to use foo_deco_factory as a context manager will not work (similarly to how using with foo won't work). Using the context manager protocol on a Decontext instance will work only if it was initialized with a context manager instance (rather than a type or factory) or if it has been called already as a decorator with appropriate arguments.

If you didn't need the decorator to be able to act as a context manager itself, you could pretty easily turn the whole class into a function (with __call__ becoming an extra level of closure, rather than a method):

def decontext(cm_factory): def factory(*cm_args, **cm_kwargs): cm = cm_factory(*cm_args, **cm_kwargs) def decorator(func): def wrapper(*args, **kwargs): with cm: return func(*args, **kwargs) return wrapper return decorator return factory 

To simplify the code in this case, I always assume you're passing in a context manager factory, rather than a context manager instance.

Sign up to request clarification or add additional context in comments.

2 Comments

Like the 2nd example & I think that's exactly what he's asked for (sounded like his helper had a sole purpose of turning his parameter-taking context manager into a decorator). Not that this is code golf, but was their a particular reason for instantiating the context manager first, then using it, versus just: with cm_factory(*cm_args, **cm_kwargs):?
@Gerrat: Hmm, I suppose there isn't a true need to create the context manager ahead of time. But one advantage of doing it this way though is that any exception (due to invalid cm_args, perhaps) will appear early on when the factory is called, not only later on when the wrapped function is called.
1

I think the following is what you're looking for. The main key is that you can't pass the context manager argument you want directly to the __call__ method on your Decontext class, so we use a helper function to do that. There's likely a way to simplify this ( but I'll leave that as an exercise to the reader :) )

from contextlib import contextmanager from functools import partial class _Decontext(object): """ makes a context manager also act as decorator """ def __init__(self, cm, *args, **kwargs): self._cm = cm self.args = args self.kwargs = kwargs def __call__(self, func): def wrapper(*args, **kwds): with self._cm(*self.args, **self.kwargs): func(*args, **kwds) return wrapper # helper for our helper :) def decontext(cm=None): def wrapper(cm, *args, **kwargs): return _Decontext(cm, *args, **kwargs) return partial(wrapper, cm) @contextmanager def sample_t(arg1): print "<%s>" % arg1 yield my_deco = decontext(sample_t) @my_deco(arg1='example') def some_func(): print 'works' if __name__ == '__main__': some_func() 

This outputs:

<example> works 

Comments

0

If I understand right, you should be doing:

@my_deco def func(arg1): print "blah" 

The decorator has to decorate something (like a function). However, there are some other problems with your class, but I'm not sure how to fix them, because it's a little hard to understand what it's supposed to do.

2 Comments

I want to pass the argument to the contextmanager sample_t through the decorator if possible.
@Warz: Have you looked at for instance this question about how to write decorators that take arguments?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.