Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to avoid "()" when writing a decorator accepting optional arguments?

37 views
Skip to first unread message

Giampaolo Rodolà

unread,
Jun 11, 2011, 3:27:03 PM6/11/11
to pytho...@python.org
I've written this decorator to deprecate a function and (optionally)
provide a callable as replacement

def deprecated(repfun=None):
"""A decorator which can be used to mark functions as deprecated.
Optional repfun is a callable that will be called with the same args
as the decorated function.
"""
def outer(fun):
def inner(*args, **kwargs):
msg = "%s is deprecated" % fun.__name__
if repfun is not None:
msg += "; use %s instead" % (repfun.__name__)
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
if repfun is not None:
return repfun(*args, **kwargs)
else:
return fun(*args, **kwargs)
return inner
return outer


Now, I can use my decorator as such:

@deprecated()
def foo():
return 0

...or provide an optional argument:

@deprecated(some_function)
def foo():
return 0

...but I don't know how to modify it so that I can omit parentheses:

@deprecated
def foo():
return 0

Any hint?


--- Giampaolo
http://code.google.com/p/pyftpdlib/
http://code.google.com/p/psutil/

Ian Kelly

unread,
Jun 11, 2011, 4:28:27 PM6/11/11
to Giampaolo Rodolà, pytho...@python.org
On Sat, Jun 11, 2011 at 1:27 PM, Giampaolo Rodolà <g.ro...@gmail.com> wrote:
>    @deprecated()
>    def foo():
>        return 0

This is equivalent to:

foo = deprecated()(foo)

>    @deprecated(some_function)
>    def foo():
>        return 0

foo = deprecated(some_function)(foo)

>    @deprecated
>    def foo():
>        return 0

foo = deprecated(foo)

You want to make the third case be equivalent to the first case. The
problem is that there is no way to distinguish between
"deprecated(foo)" (in the third case) and "deprecated(some_function)"
in the second case. Both are receiving a single argument that is a
function, but the latter needs to return a decorator while the former
needs to return a decorated function.

Since there is no way to distinguish the two cases by the arguments,
you would need a separate decorator. You could do something like
this:

deprecated_default = deprecated()

@deprecated_default
def foo():
return 0

But this hardly seems worthwhile to me just to avoid typing an extra
couple of parentheses.

Cheers,
Ian

Terry Reedy

unread,
Jun 11, 2011, 4:36:25 PM6/11/11
to pytho...@python.org
On 6/11/2011 3:27 PM, Giampaolo Rodolà wrote:
> I've written this decorator to deprecate a function and (optionally)
> provide a callable as replacement
>
> def deprecated(repfun=None):
> """A decorator which can be used to mark functions as deprecated.
> Optional repfun is a callable that will be called with the same args
> as the decorated function.
> """
> def outer(fun):
> def inner(*args, **kwargs):
> msg = "%s is deprecated" % fun.__name__
> if repfun is not None:
> msg += "; use %s instead" % (repfun.__name__)
> warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
> if repfun is not None:
> return repfun(*args, **kwargs)
> else:
> return fun(*args, **kwargs)
> return inner
> return outer
>
>
> Now, I can use my decorator as such:
>
> @deprecated()
> def foo():
> return 0
>
> ...or provide an optional argument:
>
> @deprecated(some_function)
> def foo():
> return 0
>
> ...but I don't know how to modify it so that I can omit parentheses:
>
> @deprecated
> def foo():
> return 0

These are fundamentally different beasts. One makes a call to create the
decorator. The other is the decorator. Write two decorators.
The unparameterized version is trivial.

def outer(fun):
def inner(*args, **kwargs):
msg = "%s is deprecated" % fun.__name__

warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
return fun(*args, **kwargs)
rturn inner

The parameterized version will be simpler also (with None possibility
deleted).

--
Terry Jan Reedy


zeekay

unread,
Jun 16, 2011, 7:10:03 AM6/16/11
to
I wrote a little library that does this a couple weeks ago, it's on
pypi: http://pypi.python.org/pypi/Decorum/. It's pretty simple, the
last example illustrates how to do what you want. After thinking about
it though, I think it's probably not a great idea to allow the
parenthesis to be omitted.

bruno.des...@gmail.com

unread,
Jun 17, 2011, 6:27:37 AM6/17/11
to
On Jun 11, 10:28 pm, Ian Kelly <ian.g.ke...@gmail.com> wrote:
>
> Since there is no way to distinguish the two cases by the arguments,

def deprecated(func=None, replacement=None):
if replacement:
# handle the case where a replacement has been given
elif func:
# handle the case where no replacement has been given
else:
raise ValueErrorOrSomethingLikeThis()


@deprecated(replacement=other_func):
def some_func(args):
# code here

@deprecated
def another_func(args):
# code here


My 2 cents...

Ian Kelly

unread,
Jun 17, 2011, 9:53:41 AM6/17/11
to bruno.des...@gmail.com, pytho...@python.org

That works, but I would be concerned about forgetting to specify the
argument by keyword, which would have the effect of deprecating the
replacement function and then calling it on the function that was
intended to be deprecated. Also, as in my suggestion, it doesn't seem
like a big improvement to have to type out "replacement=" when you
need the replacement function just to avoid typing "()" when you
don't.

Cheers,
Ian

bruno.des...@gmail.com

unread,
Jun 17, 2011, 10:45:31 AM6/17/11
to
On Jun 17, 3:53 pm, Ian Kelly <ian.g.ke...@gmail.com> wrote:
>
> That works, but I would be concerned about forgetting to specify the
> argument by keyword

(snip funny side effect description)

>  Also, as in my suggestion, it doesn't seem
> like a big improvement to have to type out "replacement=" when you
> need the replacement function just to avoid typing "()" when you
> don't.


I wholefully agree with you on both points. FWIW, it was just for the
sake of being technically correct which, as everyone should know, is
"the best kind of correct"), not about being practically helpful in
anyway <g>

Ethan Furman

unread,
Jun 17, 2011, 1:18:07 PM6/17/11
to Giampaolo Rodolà, pytho...@python.org
Giampaolo Rodolà wrote:
> I've written this decorator to deprecate a function and (optionally)
> provide a callable as replacement

I can see providing the replacement function so that you can say, for
example, "you are calling a deprecated function -- <function xyz> is the
replacement".

If your replacement function is drop-in compatible, though, why bother
with the whole deprecate decorator? Just drop it in! :)

~Ethan~

0 new messages