ZEE
ZEE

Reputation: 3193

Passing a parameter to the decorator in python

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

Upvotes: 7

Views: 10483

Answers (3)

alim91
alim91

Reputation: 546

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

Upvotes: 0

Vadim Zin4uk
Vadim Zin4uk

Reputation: 1796

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

Upvotes: 1

Andrew Clark
Andrew Clark

Reputation: 208405

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

Upvotes: 16

Related Questions