0

So below is a non-working example that illustrates what I'm trying to get at

class TestClass(object):

    def require_debug_mode(self, f):
        def func_wrapper(*args, **kwargs):
            if self.debug_mode:
                return f(*args, **kwargs)
        return func_wrapper

    def __init__(self, debug_mode):
        self.debug_mode = debug_mode

    @require_debug_mode
    def print_message(self, msg):
        print msg

You could re-write the desired print_message as the following:

def print_message(self, msg):
    if self.debug_mode:
        print msg

I essentially want to be able to decorate methods that will do certain checks (without having to repeat that check in every method that might use it). But these checks need access to instance level information. Is that even possible?

sedavidw
  • 11,116
  • 13
  • 61
  • 95
  • Yes, it's easily possible. You've just put the `self` parameter in the wrong place. It's the _inner_ function that needs the `self` parameter. – Aran-Fey Mar 06 '17 at 15:15

3 Answers3

1

To have the decorator as part of the class you can use this code:

class TestClass(object):

    def require_debug_mode(f):
        def func_wrapper(self, *args, **kwargs):
            if self.debug_mode:
                return f(self, *args)
        return func_wrapper

    def __init__(self, debug_mode):
        self.debug_mode = debug_mode

    @require_debug_mode
    def print_message(self, msg):
        print(msg)

tc_obj = TestClass(True)
tc_obj.print_message("debug msg")
# debug msg

tc_obj = TestClass(False)
tc_obj.print_message("debug msg")
# no output

To keep the decorator outside the class you can use the following approach

def require_debug_mode(f):
    def wrapper(*args):
        if args[0].debug_mode:
            return f(*args)
    return wrapper

class TestClass(object):
    def __init__(self, debug_mode):
        self.debug_mode = debug_mode

    @require_debug_mode
    def print_message(self, msg):
        print(msg)

tc_obj = TestClass(True)
tc_obj.print_message("debug msg")
# debug msg

tc_obj = TestClass(False)
tc_obj.print_message("debug msg")
# no output
Vikash Singh
  • 13,213
  • 8
  • 40
  • 70
0

The decorators receive the same arguments as the regular methods which means that self is available for the decorator as well. The problem with your code is that you expect the outside wrapper to get the self argument as well which is not the case (it's called at declaration time, there is no instance at that point).

For example:

import functools


class TestClass(object):

    def require_debug_mode(function):

        @functools.wraps(function)
        def _require_debug_mode(self, *args, **kwargs):
            assert self.debug_mode, 'Debug mode is required for %r' % function
            return function(self, *args, **kwargs)

        return _require_debug_mode

    def __init__(self, debug_mode):
        self.debug_mode = debug_mode

    @require_debug_mode
    def print_message(self, msg):
        print msg


test_class = TestClass(True)
test_class.print_message('ping')

test_class = TestClass(False)
test_class.print_message('pong')

Output:

ping
Traceback (most recent call last):
    ...
AssertionError: Debug mode is required for <function print_message at 0x...>
shell returned 1
Wolph
  • 78,177
  • 11
  • 137
  • 148
0

A decorator is a function that, given a function as input, returns a function as output.

You want to give as input a function print_message(self, msg). That's fine.

In order for the decorator to work effectively, the resulting function has to have similar behavior. So you want to wrap the function, decoding just enough arguments to enable "peeking" to see what you want:

def func_wrapper(self, *args, **kwargs):
    if self.debug_mode:
        return self.f(*args, **kwargs)
aghast
  • 14,785
  • 3
  • 24
  • 56