views:

92

answers:

4

I would like to make a decorator which could be used with or without a parameter : Something like this :

class d(object):
    def __init__(self,msg='my default message'):
            self.msg=msg
    def __call__(self,fn):
            def newfn():
                print self.msg
                return fn()
            return newfn

@d('This is working')
def hello():
    print 'hello world !'

@d
def too_bad():
    print 'does not work'

In my code, only the use of decorator with parameter is working : how to proceed to have both working (with and without parameter) ?

+1  A: 

You have to detect if the argument to the decorator is a function, and use a simple decorator in that case. And then you need to hope that you never need to pass only a function to the parametrized decorator.

Ignacio Vazquez-Abrams
+5  A: 

If you want to take parameters to your decorator, you need to always call it as a function:

@d():
def func():
    pass

Otherwise, you need to try to detect the difference in parameters--in other words, you need to magically guess what the caller means. Don't create an API that needs to guess; consistently say what you mean to begin with.

In other words, a function should either be a generator, or a generator factory; it shouldn't be both.

Note that if all you want to do is store a value, you don't need to write a class.

def d(msg='my default message'):
    def decorator(func):
        def newfn():
            print msg
            return func()
        return newfn
    return decorator

@d('This is working')
def hello():
    print 'hello world !'

@d()
def hello2():
    print 'also hello world'
Glenn Maynard
This is probably a good advice, but it is not correct that a function can't do both, as Ignacio Vazquez-Abrams explains. It is probably better to explain that in the answer.
Muhammad Alkarouri
@Muhammad: I didn't say it can't, I said it shouldn't.
Glenn Maynard
@Glenn: I understand. But the value of the answer would be higher if this point is explained a bit better. Just saying.
Muhammad Alkarouri
A: 

I Found an exemple , you can use @trace or @trace('msg1','msg2') : nice !

def trace(*args):
    def _trace(func):
        def wrapper(*args, **kwargs):
            print enter_string
            func(*args, **kwargs)
            print exit_string
        return wrapper
    if len(args) == 1 and callable(args[0]):
        # No arguments, this is the decorator
        # Set default values for the arguments
        enter_string = 'entering'
        exit_string = 'exiting'
        return _trace(args[0])
    else:
        # This is just returning the decorator
        enter_string, exit_string = args
        return _trace
Eric
A: 

This would work.

def d(arg):
    if callable(arg):
        def newfn():
            print 'my default message'
            return arg()
        return newfn
    else:
        def d2(fn):
            def newfn():
                print arg
                return fn()
            return newfn
        return d2

@d('This is working')
def hello():
    print 'hello world !'

@d
def hello2():
    print 'hello2 world !'

@d('Applying it twice')
@d('Would also work')
def hello3():
    print 'hello3 world !'

hello()
hello2()
hello3()

# output
#
# This is working
# hello world !
# my default message
# hello2 world !
# Applying it twice
# Would also work
# hello3 world !

If a decorator function @invocation isn't passed any explicit arguments, it is called with the function defined in the following def. If it is passed arguments, then it is first called with them and then the result of that preliminary call (which must itself also be a callable) is called with the function being defined. Either way, the return value of the last or only call is bound to the defined function name.

martineau