8

I've encountered a rather cryptic (to me at least) error message while trying to use a decorator to update a function's wrapper. Any ideas how I could remedy this?

I've tried to make my code as general as possible so it will apply to other situations as well.

def decorator(d):
    """Make function d a decorator: d wraps a function fn."""

    def _d(fn):
        return functools.update_wrapper(d(fn), fn)
    functools.update_wrapper(_d, d)
    return _d


@decorator
def f(fn):
    """Converts the string fn to a function and returns it.
    Because of the @decorator decorator, _f.__name__ should
    be identical to f.__name__"""

    f.__name__ = fn
    def _f(fn):
        return eval(fn)
    return _f

g = f('x**2')
print g.__name__

Desired output:

>>>x**2

Actual output:

Traceback (most recent call last):
  File "C:\python\swampy-2.0\testcode.py", line 18, in <module>
    g = f('x**2')
  File "C:\python\swampy-2.0\testcode.py", line 6, in _d
    return functools.update_wrapper(d(fn), fn)
  File "C:\Python27\lib\functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'str' object has no attribute '__module__'
Madison May
  • 2,723
  • 3
  • 22
  • 32

1 Answers1

6

A decorator takes a function as an argument and returns another "decorated" function. You're passing a string and attempting to return a function which is really a function factory. functools.wraps and functools.update_wrapper expect a function. A function object would have a __module__ attribute while instances of str don't have an __module__ attribute.

Do you want to generate a function from the string "x**2"?

Your implementation of decorator is unnecessary. Just use functools.wraps:

def f(fn):
    """Converts the string fn to a function and returns it."""
    @functools.wraps(fn)
    def _f(fn):
        return eval(fn)
    return _f

However, you don't want a decorator in this case but a function factory.

def factory(exp):
    def f(**kwargs):
        return eval(exp, globals(), kwargs)
    f.__name__ = exp
    return f 

Now you can use this like this:

>>> x_squared = factory("x**2")
>>> x_squared(x=7)
49

Warning: The Surgeon General Has Determined that eval is Dangerous to Your Health

stderr
  • 8,567
  • 1
  • 34
  • 50
  • Thanks for the response! Yes, I'm well aware that eval should be used with extreme caution :) In this case, though, I think I'm going to stick with it. In this particular instance, its critical that I also store the exp in _f.__name___. It doesn't seem like any of these solutions provide for that requirement. – Madison May Aug 14 '12 at 20:59
  • In this instance, what _f() is actually doing is not really my main concern (I should have made that clear in my question). I'm mainly concerned with updating the function wrapper using a decorator. – Madison May Aug 14 '12 at 21:05