Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorating an instance method and calling it from the decorator

I am using nose test generators feature to run the same test with different contexts. Since it requires the following boiler plate for each test:

class TestSample(TestBase):

    def test_sample(self):
        for context in contexts:
            yield self.check_sample, context

    def check_sample(self, context):
        """The real test logic is implemented here"""
        pass

I decided to write the following decorator:

def with_contexts(contexts=None):        
    if contexts is None:
        contexts = ['twitter', 'linkedin', 'facebook']

    def decorator(f):
        @wraps(f)
        def wrapper(self, *args, **kwargs):
            for context in contexts:
                yield f, self, context # The line which causes the error
        return wrapper
    return decorator

The decorator is used in the following manner:

class TestSample(TestBase):  

    @with_contexts()
    def test_sample(self, context):
        """The real test logic is implemented here"""
        var1 = self.some_valid_attribute

When the tests executed an error is thrown specifying that the attribute which is being accessed is not available. However If I change the line which calls the method to the following it works fine:

yield getattr(self, f.__name__), service

I understand that the above snippet creates a bound method where as in the first one self is passed manually to the function. However as far as my understanding goes the first snippet should work fine too. I would appreciate if anyone could clarify the issue.

The title of the question is related to calling instance methods in decorators in general but I have kept the description specific to my context.

like image 532
Ifthikhan Avatar asked Feb 18 '26 13:02

Ifthikhan


1 Answers

You can use functools.partial to tie the wrapped function to self, just like a method would be:

from functools import partial

def decorator(f):
    @wraps(f)
    def wrapper(self, *args, **kwargs):        
        for context in contexts:
            yield partial(f, self), context
    return wrapper

Now you are yielding partials instead, which, when called as yieldedvalue(context), will call f(self, context).

like image 152
Martijn Pieters Avatar answered Feb 20 '26 02:02

Martijn Pieters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!