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

6
  • 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'. Commented Mar 25, 2013 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... Commented Mar 25, 2013 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.) Commented Mar 25, 2013 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... Commented Mar 25, 2013 at 22:00
  • OK... I think I see the point... I will test a bit more with this info at hand... Commented Mar 25, 2013 at 22:03

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

7 Comments

PEP 318 has examples that show this pattern (enforcing type attributes or interfaces, the synchronize, etc.).
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 Thanks for the suggestion, added functools.wraps and some additional explanation.
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...
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???
|
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

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.