问题
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
回答1:
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.
回答2:
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
回答3:
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.
来源:https://stackoverflow.com/questions/25190979/passing-arguments-to-decontext-decorator