views:

315

answers:

5

I thought I understood decorators but not anymore. Do decorators only work when the function is created?

I wanted to create a series of functions that all have a required argument called 'ticket_params' that is a dictionary. and then decorate them with something like @param_checker(['req_param_1', 'req_param_2']) and then if 'req_param_1' and 'req_param_2' aren't in the dictionary, raise a custom Exception subclass. Am I thinking of this all wrong?

It would be something like this in the calling code:

@param_checker(['req_param_1', 'req_param_2'])
def my_decorated_function(params):
    # do stuff

params = {'req_param_1': 'Some Value'}
my_decorated_function(params)

# exception would be raised here from decorator.
+5  A: 

Decorators are only called once on a function, that is, when the def statement is parsed like so:

@mydecorator
def myfunction(): ...

I assume you mean something like that:

class param_checker:
  def __init__(self, l):
    self.l = l

  def __call__(self, functionToBeDecorated):
    def wrapper(*args, **kwargs):
      if any(necessary not in kwargs["ticket_params"] for necessary in self.l):
        raise MyCustomException
      return functionToBeDecorated(*args, **kwargs)

    return wrapper

Please tell me if you don't understand that ;)

AndiDog
BTW in your example it's of course easier to do a simple assertion instead of using a decorator.
AndiDog
Does this mean that each time the function is called, the function is rewritten dynamically?
orokusaki
I don't understand how returning the 'wrapper' function object calls anything. Is this because we don't need to call it because it's in the act of being called? Before, I would have imagined returning wrapper() instead, but I might be understanding now. Also, how to do an assertion instead. Thanks for this enlightening answer though because I didn't realize that you could basically override the __call__() on a function by doing this.
orokusaki
Mind that the decorator's `__call__` method is called *once* when it is *applied* to `myfunction` (same as `myfunction = param_checker(myfunction)` as others have already mentioned). So `__call__` must give back the function that will replace `myfunction`. Then, everytime you call `myfunction`, the `wrapper` function is invoked instead. As for the assertion, it would be easier in the given example to just write `assert all(necessary in params for necessary in ['req_param_1', 'req_param_2'])` as first line in orokusaki's `my_decorated_function`.
AndiDog
+11  A: 

A decorator is applied immediately after the def statement; the equivalence is:

@param_checker(['req_param_1', 'req_param_2'])
def my_decorated_function(params):
    # do stuff

is exactly the same thing as:

def my_decorated_function(params):
    # do stuff
my_decorated_function = param_checker(['req_param_1', 'req_param_2'])(my_decorated_function)

So the job of param_checker is to return a function that takes as its argument the function to be decorated and returns yet another function which does what you require. OK so far?

Edit: so, here's one implementation...:

import functools

def param_checker(reqs):
  reqs = set(reqs)
  def middling(f):
    @functools.wraps(f)
    def wrapper(params):
      missing = reqs.difference(params)
      if missing:
        raise TypeError('Missing parms: %s' % ', '.join(sorted(missing)))
      return f(params)
    return wrapper
  return middling
Alex Martelli
Sometimes I think I have a fairly good understanding of something in Python, and then Alex *explains* it and I realize that I suddenly don't understand it again. I will now have to spend the weekend meditating on a combination of functools and bellybutton lint.
Peter Rowell
@Peter, two other answers explain argument-accepting decorators as classes -- taking the args in `__init__`, then the function to decorate in `__call__`. That's OK, too, if the real thing (arg-taking decorators are functions returning a higher-order function -- specifically: returning a function that takes a function and returns a function;-) feels too weird;-). But the above example is really pretty simple (and `functools.wraps` is just a neat touch to preserve the name and docstring of the decorated function over future introspection...!).
Alex Martelli
my_decorated_function = param_checker(['req_param_1', 'req_param_2'])(my_decorated_function) . I don't understand that. What is happening by putting the decorated function in parens after the statement and how does it react to being next to the function call?
orokusaki
I know that's not the point, but it's very interesting to understand how some of this breaks down.
orokusaki
@orokusaki, both pairs of parentheses here indicate function-call. So `f=deco(parms)(f)` means functio `deco` is called with argument `parms` and returns a function that's called with argument `f` and returns a function (which is then assigned to name `f` by the `=` indicating simple assignment). This is exactly the semantics of (using `\n` to indicate linebreak since SO comments can't have linebreaks) `@deco(parms)\ndef f(...): ...`, i.e., this double calling and reassignment is exactly what decoration with a function `deco` that accepts arguments **means**.
Alex Martelli
+3  A: 

Here's a full example based on @AndiDog's example. Remember any callable can be used as a decorator, it doesn't have to be a class.

class MyCustomException(Exception):
    pass

# The decorator - instances of this class are callable as it implements __call__
class param_checker:
    # In this example l is the parameter you pass to the decorator. 
    # For example, l could be ['req_param_1', 'req_param_2'].
    def __init__(self, l):
        self.l = l

    # This makes the instance callable
    def __call__(self, functionToBeDecorated):
        def wrapper(*args, **kwargs):
            # For the successful call below args = () and
            # kwargs = {'ticket_params': {'req_param_1': 'param_1', 'req_param_2': 'param_2'}}
            if "ticket_params" not in kwargs or any(necessary not in kwargs["ticket_params"] for necessary in self.l):
                # if the ticket params parameter has not been specified, or if
                # any of the required parameters are not present raise an exception
                raise MyCustomException
            return functionToBeDecorated(*args, **kwargs)
        return wrapper

@param_checker(['req_param_1', 'req_param_2'])
def myfunction(ticket_params=None): 
    # if the two required params are present this will print
    print "params ", ticket_params

if __name__ == "__main__":
    try:
        myfunction()
    except MyCustomException:
        print "all required params not supplied"
    try:
        myfunction(ticket_params={'req_param_1': 'param_1'})
    except MyCustomException:
        print "all required params not supplied"
    myfunction(ticket_params={'req_param_1': 'param_1', 'req_param_2': 'param_2'})
John Keyes
Thanks so much John. I ended up accepting Alex's answer because he's helped me understand basic function decorators quite a bit, but Andi's answer raised questions that your answer answered for me. I voted all 3 of your answers up though.
orokusaki
It seems by this article that the self.l would actually be the function object itself: http://www.artima.com/weblogs/viewpost.jsp?thread=240808
orokusaki
I'd have accepted Alex's too, I wasn't really answering the question, just wanted to give you some working code to play with.Not sure what you mean by the second comment.
John Keyes
+2  A: 

Check Alex's answer in order to understand python decorators; by the way:

1) what aren't you understanding of decorators? Don't you understand decorators as a general concept, or Python decorators? Beware, the "classical" decorator pattern, java annotations and python decorators are different things.

2) python decorators should always return a function, e.g. in your code the return value of param_checker([...]) should be a function that accepts a function as param (the func to be decorated), and returns a function with the same signature as my_decorated_function. Take a look at the following example; the decorator function is executed just once (when the class is created), while the decorated func is then executed at every call. In this specific example, it then invokes the original func, but that's not a requirement.

def decorator(orig_func):
    print orig_func

    def decorated(self, a):
        print "aahahah", orig_func(self, a)

    return decorated


class Example(object):
    @decorator
    def do_example(self, a):
        return 2 * a


m = Example()
m.do_example(1)

3) you might not be doing the best thing in the way you're using decorators. They should usually be employed when there's some concept which is quite orthogonal to what you're actually programming, and can be reused around - it's essentially the Python way of doing AOP. Your param_checker might not be that orthogonal - if your decorator gets just used once, then it's probably not a good place to use a decorator at all. Your param_checker seems to be the case - it assumes that the decorated func takes a single arg which is a dictionary - are there many funcs in your code with such a signature and behaviour? If the answer is "no", just check for params at the begininng of the func and raise an exception if they're missing.

Alan Franzoni
A: 

This is a good explanation for those asking the same question:

# This does nothing.

class donothing(object):
    def __init__(self, func):
        """
        The 'func' argument is the function being decorated because in this
        case, we're not instantiating the decorator class. Instead we are just
        using the class object as a callable (a class is always callable as this
        is how an instance is returned) to use as a decorator, which means that
        it is being instantiated upon definition of the decorated function and
        the decorated function is being passed in as an argument to the class's
        __init__ method.
        """
        self.func = func

    def __call__(self, *args, **kwargs):
        """
        The __call__ function is called when the decorated function is called
        because the function has be eaten by the decorator class. Now it's up to
        the this method to return a call to the original function. The arguments
        are passed in as args, kwargs to be manipulated.
        """
        # Returns original function call with original arguments.
        return self.func(*args, **kwargs)

@donothing
def printer(text):
    print(text)

printer('hello world')

# The printer function is now an alias for the donothing instance created, so
# the preceding was the same as:
#
# instance = donothing(printer)
# instance('hello world')
#


# Next example:

class checkforkeysinparams(object):
    def __init__(self, required):
        self.required = set(required)

    def __call__(self, params):
        def wrapper(params):
            missing = self.required.difference(params)
            if missing:
                raise TypeError('Missing from "params" argument: %s' % ', '.join(sorted(missing)))
        return wrapper


# Apply decorator class, passing in the __init__'s 'required' argument.

@checkforkeysinparams(['name', 'pass', 'code'])
def complex_function(params):
    # Obviously these three are needed or a KeyError will be raised.
    print(params['name'])
    print(params['pass'])
    print(params['code'])


# Create params to pass in. Note, I've commented out one of the required params.

params = {
    'name': 'John Doe',
    'pass': 'OpenSesame',
    #'code': '1134',
}

# This call will output: TypeError: Missing from "params" argument: code

complex_function(params=params)
orokusaki