57

I want to import a function:

from random import randint

and then apply a decorator to it:

@decorator
randint

I was wondering if there was some syntactic sugar for this (like what I have above), or do I have to do it as follows:

@decorator
def randintWrapper(*args):
    return random.randint(*args)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
killajoule
  • 3,612
  • 7
  • 30
  • 36
  • Possible duplicate of [How can one attach a decorator to a function "after the fact" in python?](https://stackoverflow.com/questions/33868886/how-can-one-attach-a-decorator-to-a-function-after-the-fact-in-python) – darthbith Jun 30 '18 at 19:05

1 Answers1

66

Decorators are just syntactic sugar to replace a function object with a decorated version, where decorating is just calling (passing in the original function object). In other words, the syntax:

@decorator_expression
def function_name():
    # function body

roughly(*) translates to:

def function_name():
    # function body
function_name = decorator_expression(function_name)

In your case, you can apply your decorator manually instead:

from random import randint

randint = decorator(randint)

(*) When using @<decorator> on a function or class, the result of the def or class definition is not bound (assigned to their name in the current namespace) first. The decorator is passed the object directly from the stack, and only the result of the decorator call is then bound.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 3
    The only problem is that the docstring and method name is not preserved. Maybe want to use `functools.wraps` – pratikm Mar 01 '16 at 21:06
  • 5
    @pratikm: that's a separate issue though. The decorator should indeed use `functools.wraps`, but that doesn't change how can you *apply* the decorator. – Martijn Pieters Mar 01 '16 at 22:39
  • This doesn't seem to work for flask route decorators. That is `@app.route('/route', methods=['GET'])`. – CMCDragonkai Jul 27 '18 at 05:41
  • 7
    @CMCDragonkai: it works jut fine for Flask route decorators, provided you call the result of the decorator factory: `app.route('/route', methods=['GET'])(view_func)` (no need to capture the return value, since the `app.route()` decorator registers, and returns the view function unaltered). Not that you should do that however, since you could just use [`app.add_url_rule()`](http://flask.pocoo.org/docs/0.12/api/#flask.Flask.add_url_rule) instead: `app.add_url_rule('/route', view_func.__name__, view_func, methods=['GET'])`. – Martijn Pieters Jul 27 '18 at 09:36
  • 1
    The clearest example I have seen so far. Note that your decorator itself may be a called function rather than the function name. E.g. I had to do `torch.cuda.amp.autocast()(model.forward)` It is weird because there are 2 parentheses next to each other, but that is the consequence of being a programmer. – Corey Levinson Sep 09 '20 at 05:50
  • 3
    @CoreyLevinson the term for those is a decorator *factory*. Like the Flask `route` decorator factory, it’s the call that returns the actual decorator. This is generally used to configure the decorator that’s being produced. E.g. `autocast()` takes an `enabled` parameter. – Martijn Pieters Sep 09 '20 at 07:09