views:

58

answers:

3

I've been playing around in depth with attempting to write my own version of a memoizing decorator before I go looking at other people's code. It's more of an exercise in fun, honestly. However, in the course of playing around I've found I can't do something I want with decorators.

def addValue( func, val ):
    def add( x ):
        return func( x ) + val
    return add

@addValue( val=4 )
def computeSomething( x ):
    #function gets defined

If I want to do that I have to do this:

def addTwo( func ):
    return addValue( func, 2 )

@addTwo
def computeSomething( x ):
    #function gets defined

Why can't I use keyword arguments with decorators in this manner? What am I doing wrong and can you show me how I should be doing it?

+5  A: 

You need to define a function that returns a decorator:

def addValue(val):
    def decorator(func):
        def add(x):
            return func(x) + val
        return add
    return decorator

When you write @addTwo, the value of addTwo is directly used as a decorator. However, when you write @addValue(4), first addValue(4) is evaluated by calling the addValue function. Then the result is used as a decorator.

interjay
So you're saying that in Python decorators employ eager evaluation? What a bummer.
wheaties
+3  A: 

You want to partially apply the function addValue - give the val argument, but not func. There are generally two ways to do this:

The first one is called currying and used in interjay's answer: instead of a function with two arguments, f(a,b) -> res, you write a function of the first arg that returns another function that takes the 2nd arg g(a) -> (h(b) -> res)

The other way is a functools.partial object. It uses inspection on the function to figure out what arguments a function needs to run (func and val in your case ). You can add extra arguments when creating a partial and once you call the partial, it uses all the extra arguments given.

from functools import partial
@partial(addValue, val=2 ) # you can call this addTwo
def computeSomething( x ): 
    return x

Partials are usually a much simpler solution for this partial application problem, especially with more than one argument.

THC4k
functools.partial, very nice. I knew there was a simple way to get currying accomplished in Python without having to triple indent. Thanks much.
wheaties
+3  A: 

Decorators with any kinds of arguments -- named/keyword ones, unnamed/positional ones, or some of each -- essentially, ones you call on the @name line rather than just mention there -- need a double level of nesting (while the decorators you just mention have a single level of nesting). That goes even for argument-less ones if you want to call them in the @ line -- here's the simplest, do-nothing, double-nested decorator:

def double():
  def middling():
    def inner(f):
      return f
    return inner
  return middling

You'd use this as

@double()
def whatever ...

note the parentheses (empty in this case since there are no arguments needed nor wanted): they mean you're calling double, which returns middling, which decorates whatever.

Once you've seen the difference between "calling" and "just mentioning", adding (e.g. optional) named args is not hard:

def doublet(foo=23):
  def middling():
    def inner(f):
      return f
    return inner
  return middling

usable either as:

@doublet()
def whatever ...

or as:

@doublet(foo=45)
def whatever ...

or equivalently as:

@doublet(45)
def whatever ...
Alex Martelli