7

Why is this decorator with a parameter not working?

def decAny( f0 ):
    def wrapper( s0 ):
        return "<%s> %s </%s>" % ( any, f0(), any )
    return wrapper

@decAny( 'xxx' )
def test2():
    return 'test1XML'

print( test2() )

always gives me an error saying "str is not callable" it is trying to execute the return string inside the wrapper() instead of processing it and return the result string

bereal
  • 32,519
  • 6
  • 58
  • 104
ZEE
  • 2,931
  • 5
  • 35
  • 47
  • 1
    Think about it this way: Before you even _get_ to decorating `test2`, you're calling `decAny('xxx')`. But `decAny` takes a function, `f0`, not a string. So clearly at some point, that `f0()` is going to try to call `'xxx'`. – abarnert Mar 25 '13 at 21:16
  • Ok, but like in a decorator with no parameters why the compiler not assume that the first parameter is the client function... – ZEE Mar 25 '13 at 21:48
  • 1
    It's not a matter of parameters. If you have `@decAny`, that's just using `decAny` itself as a decorator. But if you have `@decAny()`, that's calling `decAny` before you even get to decorating, just as `@decAny('xxx')` is. (It's just like when you pass functions as values, store them in variables, etc., as opposed to calling them.) – abarnert Mar 25 '13 at 21:57
  • "you're calling decAny('xxx'). But decAny takes a function, f0, not a string" The declared parameters in the decorator should be passed to the client function... that would simplify and bring intuitiveness to the decorators with parameters--- Decorators with no parameters work as should... the problem is in the definition for parameteres in the decorator... should be more refined and simplified... – ZEE Mar 25 '13 at 22:00
  • OK... I think I see the point... I will test a bit more with this info at hand... – ZEE Mar 25 '13 at 22:03
  • The first draft of the PEP actually gave almost your exact example, but after defining something like your `decAny`, it then did something like `bold = decAny('b')`, `italic = decAny('i')`, etc., which allows you to just do `@bold` (with no parameters). Basically, the reason decorators work like this is the same reason just referring to a function returns the value instead of calling it (as opposed to, say, Ruby). – abarnert Mar 25 '13 at 22:08

3 Answers3

16

Decorators are functions that return functions. When "passing a parameter to the decorator" what you are actually doing is calling a function that returns a decorator. So decAny() should be a function that returns a function that returns a function.

It would look something like this:

import functools

def decAny(tag):
    def dec(f0):
        @functools.wraps(f0)
        def wrapper(*args, **kwargs):
            return "<%s> %s </%s>" % (tag, f0(*args, **kwargs), tag)
        return wrapper
    return dec

@decAny( 'xxx' )
def test2():
    return 'test1XML'

Example:

>>> print(test2())
<xxx> test1XML </xxx>

Note that in addition to fixing the specific problem you were hitting I also improved your code a bit by adding *args and **kwargs as arguments to the wrapped function and passing them on to the f0 call inside of the decorator. This makes it so you can decorate a function that accepts any number of positional or named arguments and it will still work correctly.

You can read up about functools.wraps() here:
http://docs.python.org/2/library/functools.html#functools.wraps

Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • [PEP 318](http://www.python.org/dev/peps/pep-0318/) has examples that show this pattern (enforcing type attributes or interfaces, the `synchronize`, etc.). – abarnert Mar 25 '13 at 21:15
  • 1
    If you're going to add the `*args, **kwargs` to improve his code (without explaining why), you probably also want to add `functools.wraps`. – abarnert Mar 25 '13 at 21:17
  • @abarnert Thanks for the suggestion, added `functools.wraps` and some additional explanation. – Andrew Clark Mar 25 '13 at 21:22
  • this seems to be a function factory... I know decorator mechanics from other languages and the python way seems to complicate things thar are simple... – ZEE Mar 25 '13 at 21:52
  • The decorator directive @decorator assumes the next line "function result" as the data to be decorated... the 1st implicit parameter of the decorator is the function who resides in the line after the decorator... Why complicate things... the compiler/interpreter should assume the "function to wrap" as the 1st parameter and if decorator has parameters pass them all to the "wrapper"... This way shoul avoid the Factory pattern that is more expensive and less intuitive!!! What am I missing here??? – ZEE Mar 25 '13 at 21:57
  • @ZEE: You're not forced to use a factory, and in fact it's easier if you don't. You can use any callable you want as a decorator. If you want that callable to be something returned by a factory function, you can, but in that case, obviously your factory function has to be a factory function. And if you're just looking to bind arguments, you can always use `partial` or define a local closure without building another layer on top with a factory. – abarnert Mar 25 '13 at 22:01
  • **From the point of view of the Python language definition, the Python decoration mechanism is indeed quite simple**: The whole decorator expression (whether with arguments or without) is simply evaluated and the result is call-applied to the thing being decorated. (From the programmer's point of view, that makes decorators with parameters admittedly confusing.) – Lutz Prechelt Mar 24 '14 at 20:54
1

There is a good sample from "Mark Lutz - Learning Python" book:

def timer(label=''):
    def decorator(func):
        def onCall(*args):   # Multilevel state retention:
            ...              # args passed to function
            func(*args)      # func retained in enclosing scope
            print(label, ... # label retained in enclosing scope
        return onCall
    return decorator         # Returns the actual decorator

@timer('==>')                # Like listcomp = timer('==>')(listcomp)
def listcomp(N): ...         # listcomp is rebound to new onCall

listcomp(...)                # Really calls onCall
Vadim Zin4uk
  • 1,716
  • 22
  • 18
0

there is another to implement decorator using class and you can also pass arguments to the decorator itself

here is an example of a logger helper decorator, which you can pass the function scope and the returned value when it fails

import logging

class LoggerHelper(object):

    def __init__(self, scope, ret=False):
        self.scope = scope
        self.ret = ret

    def __call__(self, original_function):
        def inner_func(*args, **kwargs):
            try:
                logging.info(f"*** {self.scope} {original_function.__name__} Excuting ***")
                return original_function(*args, **kwargs)
                logging.info(f"*** {self.scope} {original_function.__name__} Executed Successfully ***")
            except Exception as e:
                logging.error(f"*** {self.scope} {original_function.__name__} Error: {str(e)} ***")
                return self.ret
            
        return inner_func

and when you use it, you can easily track where the exception was raised

class Example:

    @LoggerHelper("Example", ret=False)
    def method:
        print(success)
        return True
alim91
  • 538
  • 1
  • 8
  • 17