5

I know there is similar question, but my scenario is somehow different: refer to codes:

class MyClass(object):
    def __init__(self, log_location)
        self.logs = logging(log_location) # create log object by the log_location, this object should be used by the decorator fucntion

    def record_log(log_object): 
        """ this is the decorator function
        """
        def deco(func):
            def wrap(*args, **kwargs):
                rs = func()

                # use log object to record log
                if rs:
                    log_object.record('success')
                else:
                    log_object.record('fail')

            return wrap
        return deco

   @record_log(self.logs) 
   def test(self):
       rs = do_some_thing
       if rs:
            return True
       return False

def main():
    my_class = MyClass()
    my_class.test()   

But, there is an error like this:

@record_log(self.logs)
NameError: name 'self' is not defined

Hos should I use the instance attribute self.logs in a decorator function in such scenario like this??

Thanks very much!

Spybdai
  • 179
  • 6

2 Answers2

2

You can not pass a reference to self or any attribute of self at this point. The @record_log line is executed (the method is decorated) before the code in main is executed, i.e. before any instance of MyClass is created at all -- in fact, even before the definition of MyClass has been completed! But remember that

@record_log(self.logs) 
def test(self, n):

is actually just syntactic sugar for

test = record_log(self.logs)(test)

So one way to work around your problem would be to redefine test in your __init__, i.e.

def __init__(self, log_location)
    self.logs = logging(log_location)
    self.test = record_log(self.logs)(self.test)

Also note that your decorator is not passing any parameters to func and not returning the results. Also, it should probably be defined on module level (before the class).

def record_log(log_object): 
    def deco(func):
        def wrap(*args, **kwargs):
            rs = func(*args, **kwargs)   # pass parameters
            if rs:
                log_object.record('success')
            else:
                log_object.record('fail')
            return rs   # return result
        return wrap
    return deco
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • Hi tobias_k, thanks for your help. It works now, even though a little ugly. And another question is: as @record_log(self.logs) def test(self, n): is actually just syntactic sugar for test = record_log(self.logs)(test) why one work, while the other doesn't??? – Spybdai Apr 22 '15 at 09:51
  • @Spybdai Because, as I said, the `@record_log` statement is executed _before_ the class is even fully created, while the `self.test = ...` is executed when an instance of the class is being created. – tobias_k Apr 22 '15 at 09:58
1

There are several objections about your code:

  1. deco() is redundant. You can directly return wrap from record_log().

  2. If you only plan to decorate MyClass's methods, then there is no point in passing log_object to the decorator, as self.logs will always be used. Otherwise, consider moving the decorator to module level, as already suggested by others.

  3. The decorated method's return value is currently lost.

  4. The call to the decorated function does not pass self to it.

The proper code would therefore be:

class MyClass(object):
    def __init__(self, log_location):
        self.logs = logging(log_location)

    def record_log(func):
        """ this is the decorator function
        """
        def wrap(self):
            rs = func(self)
            # use log object to record log
            if rs:
                print 1
                self.logs.record('success')
            else:
                print 2
                self.logs.record('fail')
            return rs
        return wrap

    @record_log
    def test(self):
       rs = do_some_thing
       if rs:
            return True
       return False
Vadim Landa
  • 2,784
  • 5
  • 23
  • 33
  • 1
    Nice... but I think you should use `*args, **kwargs` as parameters and get `self` from `args[0]`, so the decorator is applicable to other methods of the class. – tobias_k Apr 22 '15 at 09:42
  • Thanks Vadim for the detailed comments about the code and thanks for the solution. Thanks @tobias_k also very much, but this solution is better for my scenario. thanks all the guys for the help!!! – Spybdai Apr 22 '15 at 10:12
  • Don't forget to keep @tobias_k 's comments in mind as well :) – Vadim Landa Apr 22 '15 at 10:13
  • Hi @VadimLanda, another 2 questions: 1. there is no 'self' parameter for function 'rocord_log', is it a static method? If not, what's the difference between it and a static method wrapped by 'staticmethod' ? 2. as you mentioned in the 3rd point, "The decorated method's return value is currently lost", why there must be return value in a decorator? – Spybdai Apr 23 '15 at 02:21
  • 1. Technically, `record_log` is a static method, however, you cannot use `@staticmethod` with that - see http://stackoverflow.com/questions/6412146/python-decorator-as-a-staticmethod for details. 2. As decorators are wrappers around functions, then in most cases, you'll still want to know the original function's result. Of course, you may discard that in the decorator if you don't care about it. – Vadim Landa Apr 23 '15 at 02:50