Passing arguments to decontext decorator

£可爱£侵袭症+ 提交于 2019-12-11 09:54:38

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!