0

Following up on an earlier thread my question is how can I take this expression: fn(self,*args, **kwargs and call it in oo fashion like this self.fn(...) here is my total program with the failing line commented out:

def formatHeader(fn):
    def wrapped(*args, **kwargs):
        print "here is args prior to extraction - {0}".format(args)
        if len(args) > 1:
            self,args = args  # remove self from args
        else:
            self, args= args[0], ()
        print("Here are the arguments after extraction: {0} {1}".format(self, args))
        #return '<div class="page_header">' + self.fn(*args, **kwargs)+'</div>'
        return '<div class="page_header">' + fn(self,*args, **kwargs)+'</div>'
    return wrapped    

class MyPage(object):
    def __init__(self):
        self.PageName = ''

    def createPage(self):
        pageHeader = self.createHeader()
        return pageHeader

    def addem(self, a, b):
        return a + b

    @formatHeader   #<----- decorator
    def createHeader(self):
        return "Page Header " + self.PageName


obj = MyPage()

print obj.createHeader()
Community
  • 1
  • 1
Terrence Brannon
  • 4,760
  • 7
  • 42
  • 61
  • Please fix your formatting. Looks like not all your code made it in to the code block. – Silas Ray Aug 01 '12 at 15:37
  • 1
    ... and what's wrong with the way things are right now? Decorators are applied to functions, they don't care about classes, and neither should you when writing one. (Also, your code gets usable outside a class if you keep things the way they are right now) – Gabi Purcaru Aug 01 '12 at 15:41

2 Answers2

3

First, self doesn't exist in that scope. self is a label that by convention refers to the current instance within instance methods of a class. Second, decorators are not meant to be aware of the instance of the function they are wrapping(at least by default). The method is bound to the instance, and the decorator operates on the bound method it was passed as a black box callable. If you want to have access to the instance members from within the decorator (which you shouldn't, since that actually breaks oo), you'll have to pass the instance to the decorator, which means enclosing the decorator in a further closure, or using introspection to dynamically discover the owning instance from within the decorator code.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
1

The problem is your wrapper wants to access another method of the instance to which it's being applied, but it's not passed one until runtime -- as opposed to class definition time which is when the decorator runs). Basically you need an instance specific method decorator. You can accomplish this by making the decorator a descriptor as described in the class method decorator using instance recipe from the PythonDecoratorLibrary.

Here's it applied to your sample code (with some cosmetic changes):

from functools import wraps

def formatHeader(fn):

    class Descriptor(object):
        def __init__(self, fn):
            self.fn = fn

        def __get__(self, instance, klass):
            if instance is None:  # Class method was requested
                return self.make_unbound(klass) #  will raise TypeError
            return self.make_bound(instance)

        def make_unbound(self, klass):
            @wraps(self.fn)
            def wrapped(*args, **kwargs):
                '''This documentation will vanish :)'''
                raise TypeError(
                    'unbound method {}() must be called with {} instance '
                    'as first argument (got nothing instead)'.format(
                        self.fn.__name__, klass.__name__)
                )
            return wrapped

        def make_bound(self, instance):
            @wraps(self.fn)
            def wrapped(*args, **kwargs):
                '''This documentation will disapear :)'''
                return ('<div class="page_header">' +
                        self.fn(instance, *args, **kwargs) + '</div>')

            # This instance does not need the descriptor anymore,
            # let it find the wrapped method directly next time:
            setattr(instance, self.fn.__name__, wrapped)
            return wrapped

    return Descriptor(fn)

class MyPage(object):
    def __init__(self):
        self.PageName = ''

    def createPage(self):
        pageHeader = self.createHeader()
        return pageHeader

    def addem(self, a, b):
        return a + b

    @formatHeader   #<----- decorator
    def createHeader(self):
        return "Page Header " + self.PageName


obj = MyPage()

print obj.createHeader()

Note that the self argument in the nested Descriptor class methods refers to an instance of the Descriptor class not the class whose method was wrapped (that's instance in the code).

martineau
  • 119,623
  • 25
  • 170
  • 301