views:

1101

answers:

3

I want to construct classes for use as decorators with the following principles intact:

  1. It should be possible to stack multiple such class decorators on top off 1 function.
  2. The resulting function name pointer should be indistinguishable from the same function without a decorator, save maybe for just which type/class it is.
  3. Ordering off the decorators should not be relevant unless actually mandated by the decorators. Ie. independent decorators could be applied in any order.

This is for a Django project, and the specific case I am working on now the method needs 2 decorators, and to appear as a normal python function:

@AccessCheck
@AutoTemplate
def view(request, item_id) {}

@AutoTemplate changes the function so that instead of returning a HttpResponse, it just returns a dictionary for use in the context. A RequestContext is used, and the template name is inferred from the method name and module.

@AccessCheck adds additional checks on the user based on the item_id.

I am guessing it's just to get the constructor right and copy the appropriate attributes, but which attributes are these?

The following decorator won't work as I describe:

class NullDecl (object):
    def __init__ (self, func):
        self.func = func
    def __call__ (self, * args):
        return self.func (*args)

As demonstrated by the following code:

@NullDecl
@NullDecl
def decorated():
    pass

def pure():
    pass

# results in set(['func_closure', 'func_dict', '__get__', 'func_name',
# 'func_defaults', '__name__', 'func_code', 'func_doc', 'func_globals'])
print set(dir(pure)) - set(dir(decorated));

Additionally, try and add "print func.name" in the NullDecl constructor, and it will work for the first decorator, but not the second - as name will be missing.

Refined eduffy's answer a bit, and it seems to work pretty well:

class NullDecl (object):
    def __init__ (self, func):
        self.func = func
        for n in set(dir(func)) - set(dir(self)):
            setattr(self, n, getattr(func, n))

    def __call__ (self, * args):
        return self.func (*args)
    def __repr__(self):
        return self.func
+5  A: 

A do-nothing decorator class would look like this:

class NullDecl (object):
   def __init__ (self, func):
      self.func = func
      for name in set(dir(func)) - set(dir(self)):
        setattr(self, name, getattr(func, name))

   def __call__ (self, *args):
      return self.func (*args)

And then you can apply it normally:

@NullDecl
def myFunc (x,y,z):
   return (x+y)/z
eduffy
Check my comment for why your code won't work as I stated.
Staale
I added my own fix so it works properly. Thanks for the help, usually helps to start very basic.
Staale
+7  A: 

The decorator module helps you writing signature-preserving decorators.

And the PythonDecoratorLibrary might provide useful examples for decorators.

f3lix
+2  A: 

To create a decorator that wraps functions in a matter that make them indistinguishable from the original function, use functools.wraps.

Example:


def mydecorator(func):
    @functools.wraps(func):
    def _mydecorator(*args, **kwargs):
        do_something()
        try:
            return func(*args, **kwargs)
        finally:
            clean_up()
    return _mydecorator

# ... and with parameters
def mydecorator(param1, param2):
    def _mydecorator(func):
        @functools.wraps(func)
        def __mydecorator(*args, **kwargs):
            do_something(param1, param2)
            try:
                return func(*args, **kwargs)
            finally:
                clean_up()
        return __mydecorator
    return _mydecorator

(my personal preference is to create decorators using functions, not classes)

The ordering of decorators is as follows:


@d1
@d2
def func():
    pass

# is equivalent to
def func():
    pass

func = d1(d2(func))
codeape
I prefer to use classes, but I guess it's due to my Java background. I think I read someone recommending using classes for decorators, but I guess that was bad advice.
Staale
If you want to use classes, I am not sure where you put the @functools.wraps. I feel functions as decorators are more pythonic. And the code is shorter.
codeape
In your parameters example, where does "func" come from? It's not in the list of parameters for mydecorator()...
Aaron Digulla
You apply @decorator(param1, param2) def func: pass - decorator(param1, param2) returns the actual function that will be used for decorating. There are 3 levels of nested functions here.
Staale