Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #8539 > unrolled thread

Using decorators with argument in Python

Started byJigar Tanna <poisonousrattle5@gmail.com>
First post2011-06-28 09:52 -0700
Last post2011-06-29 08:19 +1000
Articles 12 — 7 participants

Back to article view | Back to comp.lang.python


Contents

  Using decorators with argument in Python Jigar Tanna <poisonousrattle5@gmail.com> - 2011-06-28 09:52 -0700
    Re: Using decorators with argument in Python Lie Ryan <lie.1296@gmail.com> - 2011-06-29 03:06 +1000
      Re: Using decorators with argument in Python John Posner <jjposner@codicesoftware.com> - 2011-06-29 13:23 -0400
      Re: Using decorators with argument in Python Ethan Furman <ethan@stoneleaf.us> - 2011-06-29 12:30 -0700
      Re: Using decorators with argument in Python Ian Kelly <ian.g.kelly@gmail.com> - 2011-06-29 13:29 -0600
      Re: Using decorators with argument in Python Ethan Furman <ethan@stoneleaf.us> - 2011-06-29 14:29 -0700
      Re: Using decorators with argument in Python Ian Kelly <ian.g.kelly@gmail.com> - 2011-06-29 15:26 -0600
      Re: Using decorators with argument in Python Ethan Furman <ethan@stoneleaf.us> - 2011-06-29 14:51 -0700
      Re: Using decorators with argument in Python Duncan Booth <duncan.booth@invalid.invalid> - 2011-06-30 09:00 +0000
      Re: Using decorators with argument in Python John Posner <jjposner@codicesoftware.com> - 2011-07-01 11:18 -0400
    Re: Using decorators with argument in Python Ian Kelly <ian.g.kelly@gmail.com> - 2011-06-28 11:20 -0600
    Re: Using decorators with argument in Python Ben Finney <ben+python@benfinney.id.au> - 2011-06-29 08:19 +1000

#8539 — Using decorators with argument in Python

FromJigar Tanna <poisonousrattle5@gmail.com>
Date2011-06-28 09:52 -0700
SubjectUsing decorators with argument in Python
Message-ID<d35e1e82-410b-471b-a3c7-379c1822adb0@m22g2000yqh.googlegroups.com>
I am new to Python and Django, was going through the concept of
decorators
where I came across a special case of using arguments with decorators
Below is the code for memoization where I was looking at the
concept...

cache = {}
def get_key(function, *args, **kw) :
    key = '%s. %s: ' % (function. __module__,function. __name__)
    hash_args = [ str(arg) for arg in args]
    hash_kw = [' %s: %s' % (k, hash(v) )
               for k, v in kw.items() ]
    return ' %s:: %s: : %s' % (key, hash_args, hash_kw)

def memoize(get_key=get_key, cache=cache) :
    def _memoize( function) :
        print function
        def __memoize(*args, **kw) :
            key = get_key(function, *args, **kw)
            try:
                return cache[key]
            except KeyError:
                cache[key] = function( *args, **kw)
                return cache[key]
        return __memoize
    return _memoize

@memoize()
def factory(n) :
    return n * n

# testcase
#print factory(3)
#
#

coming across to certain views from people, it is not a good practice
to use
decorators with arguments (i.e. @memoize() ) and instead it is good to
just
use @memoize. Can any of you guys explain me advantages and
disadvantages of
using each of them

[toc] | [next] | [standalone]


#8540

FromLie Ryan <lie.1296@gmail.com>
Date2011-06-29 03:06 +1000
Message-ID<4e0a0a8b$1@dnews.tpgi.com.au>
In reply to#8539
On 06/29/2011 02:52 AM, Jigar Tanna wrote:

> coming across to certain views from people, it is not a good practice
> to use
> decorators with arguments (i.e. @memoize() ) and instead it is good to
> just
> use @memoize. Can any of you guys explain me advantages and
> disadvantages of
> using each of them

Simplicity is one, using @decor() means you have at least three-level
nested functions, which means the code is likely to be very huge and
perhaps unnecessarily.

However, there is nothing wrong with using decorators with arguments;
except that if you have a simpler alternative, then why use the more
complex ones?

[toc] | [prev] | [next] | [standalone]


#8573

FromJohn Posner <jjposner@codicesoftware.com>
Date2011-06-29 13:23 -0400
Message-ID<mailman.502.1309370011.1164.python-list@python.org>
In reply to#8540
On 2:59 PM, Lie Ryan wrote:
>> Can any of you guys explain me advantages and disadvantages of
>> using each of them
> Simplicity is one, using @decor() means you have at least three-level
> nested functions, which means the code is likely to be very huge and
> perhaps unnecessarily.

Bruce Eckel pointed out (
http://www.artima.com/weblogs/viewpost.jsp?thread=240808) that the
result of "decoration" need not be a function. Instead, it can be an
object (instance of a user-defined class) that's callable (because the
class implements a __call__ method).

Investigating how this fact fit in with the current thread, I came up
with an alternative to the three levels of "def" (pronounced "three
levels of death"). Following is code for two decorators:

* the first one encloses the output of a function with lines of "#"
characters, and is used like this:

     @enclose
     myfun(...

* the second one encloses the output of a function with lines of a
user-specified character, and is used like this:

     @enclose("&")
     myfun(...

Here's the Python2 code for each one:

################## decorator to be called with no argument

class enclose:
    """
    class that can be used as a function decorator:
    prints a line of "#" before/after the function's output
    """
    def __init__(self, funarg):
        self.func = funarg
    def __call__(self, *args, **kwargs):
        print "\n" + "#" * 50
        self.func(*args, **kwargs)
        print "#" * 50 + "\n"

################## decorator to be called with argument

def enclose(repeat_char):
    """
    function that returns a class that can be used as a decorator:
    prints a line of <repeat_char> before/after the function's output
    """
    class _class_to_use_as_decorator:
        def __init__(self, funarg):
            self.func = funarg
        def __call__(self, *args, **kwargs):
            print "\n" + repeat_char * 50
            self.func(*args, **kwargs)
            print repeat_char * 50 + "\n"

    return _class_to_use_as_decorator


Best,
John

[toc] | [prev] | [next] | [standalone]


#8576

FromEthan Furman <ethan@stoneleaf.us>
Date2011-06-29 12:30 -0700
Message-ID<mailman.504.1309374983.1164.python-list@python.org>
In reply to#8540
John Posner wrote:
> Investigating how this fact fit in with the current thread, I came up
> with an alternative to the three levels of "def" (pronounced "three
> levels of death"). Following is code for two decorators:
> 
> * the first one encloses the output of a function with lines of "#"
> characters, and is used like this:
> 
>      @enclose
>      myfun(...
> 
> * the second one encloses the output of a function with lines of a
> user-specified character, and is used like this:
> 
>      @enclose("&")
>      myfun(...
> 
> Here's the Python2 code for each one:

[snippety-snip]

How about just having one bit of code that works either way?

8<------------------------------------------------------------------
class enclose(object):
     def __init__(self, char='#'):
         self.char = char
         if callable(char):  # was a function passed in directly?
             self.char = '#' # use default char
             self.func = char
     def __call__(self, func=None):
         if func is None:
             return self._call()
         self.func = func
         return self
     def _call(self):
         print("\n" + self.char * 50)
         self.func()
         print(self.char * 50 + '\n')

if __name__ == '__main__':
     @enclose
     def test1():
         print('Spam!')

     @enclose('-')
     def test2():
         print('Eggs!')

     test1()
     test2()
8<------------------------------------------------------------------

Output:

##################################################
Spam!
##################################################


--------------------------------------------------
Eggs!
--------------------------------------------------

8<------------------------------------------------------------------


~Ethan~

[toc] | [prev] | [next] | [standalone]


#8578

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-06-29 13:29 -0600
Message-ID<mailman.505.1309375961.1164.python-list@python.org>
In reply to#8540
On Wed, Jun 29, 2011 at 1:30 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
> How about just having one bit of code that works either way?

How would you adapt that code if you wanted to be able to decorate a
function that takes arguments?

This also won't work if the argument to the decorator is itself a
callable, such as in the OP's example.

[toc] | [prev] | [next] | [standalone]


#8585

FromEthan Furman <ethan@stoneleaf.us>
Date2011-06-29 14:29 -0700
Message-ID<mailman.511.1309382125.1164.python-list@python.org>
In reply to#8540
Ian Kelly wrote:
> On Wed, Jun 29, 2011 at 1:30 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
>> How about just having one bit of code that works either way?
> 
> How would you adapt that code if you wanted to be able to decorate a
> function that takes arguments?

8<----------------------------------------------------------------
class enclose(object):
     func = None
     def __init__(self, char='#'):
         self.char = char
         if callable(char):  # was a function passed in directly?
             self.char = '#' # use default char
             self.func = char
     def __call__(self, func=None, *args, **kwargs):
         if self.func is None:
             self.func = func
             return self
         if func is not None:
             args = (func, ) + args
         return self._call(*args, **kwargs)
     def _call(self, *args, **kwargs):
         print("\n" + self.char * 50)
         self.func(*args, **kwargs)
         print(self.char * 50 + '\n')
if __name__ == '__main__':
     @enclose
     def test1():
         print('Spam!')
     @enclose('-')
     def test2():
         print('Eggs!')
     @enclose
     def test3(string):
         print(string)
     @enclose('^')
     def test4(string):
         print(string)
     test1()
     test2()
     test3('Python')
     test4('Rules!  ;)')
8<----------------------------------------------------------------

> This also won't work if the argument to the decorator is itself a
> callable, such as in the OP's example.

Indeed.  In that case you need two keywords to __init__, and the 
discipline to always use the keyword syntax at least for the optional 
function paramater.  On the bright side, if one forgets, it blows up 
pretty quickly.

Whether it's worth the extra effort depends on the programmer's tastes, 
of course.

8<----------------------------------------------------------------
class enclose(object):
     func = None
     pre_func = None
     def __init__(self, dec_func=None, opt_func=None):
         if opt_func is None:
             if dec_func is not None: # was written without ()'s
                 self.func = dec_func
         else:
             self.pre_func = opt_func
     def __call__(self, func=None, *args, **kwargs):
         if self.func is None:
             self.func = func
             return self
         if func is not None:
             args = (func, ) + args
         if self.pre_func is not None:
             self.pre_func()
         return self._call(*args, **kwargs)
     def _call(self, *args, **kwargs):
         print("\n" + '~' * 50)
         self.func(*args, **kwargs)
         print('~' * 50 + '\n')

if __name__ == '__main__':
     def some_func():
         print('some func here')
     @enclose
     def test1():
         print('Spam!')
     @enclose(opt_func=some_func)
     def test2():
         print('Eggs!')
     @enclose
     def test3(string):
         print(string)
     @enclose(opt_func=some_func)
     def test4(string):
         print(string)
     test1()
     test2()
     test3('Python')
     test4('Rules!  ;)')
8<----------------------------------------------------------------

~Ethan~

[toc] | [prev] | [next] | [standalone]


#8587

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-06-29 15:26 -0600
Message-ID<mailman.513.1309382813.1164.python-list@python.org>
In reply to#8540
On Wed, Jun 29, 2011 at 3:29 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
> 8<----------------------------------------------------------------
> class enclose(object):
>    func = None
>    def __init__(self, char='#'):
>        self.char = char
>        if callable(char):  # was a function passed in directly?
>            self.char = '#' # use default char
>            self.func = char
>    def __call__(self, func=None, *args, **kwargs):
>        if self.func is None:
>            self.func = func
>            return self
>        if func is not None:
>            args = (func, ) + args
>        return self._call(*args, **kwargs)
>    def _call(self, *args, **kwargs):
>        print("\n" + self.char * 50)
>        self.func(*args, **kwargs)
>        print(self.char * 50 + '\n')
> if __name__ == '__main__':
>    @enclose
>    def test1():
>        print('Spam!')
>    @enclose('-')
>    def test2():
>        print('Eggs!')
>    @enclose
>    def test3(string):
>        print(string)
>    @enclose('^')
>    def test4(string):
>        print(string)
>    test1()
>    test2()
>    test3('Python')
>    test4('Rules!  ;)')
> 8<----------------------------------------------------------------


@enclose
def test5(string, func):
     print(func(string))
test5('broken', func=str.upper)

[toc] | [prev] | [next] | [standalone]


#8589

FromEthan Furman <ethan@stoneleaf.us>
Date2011-06-29 14:51 -0700
Message-ID<mailman.515.1309383412.1164.python-list@python.org>
In reply to#8540
Ian Kelly wrote:
> 
> @enclose
> def test5(string, func):
>      print(func(string))
> test5('broken', func=str.upper)

Yes, that is a limitation -- one loses the func keyword for the 
decorated function.  If I were to actually use this, I'd probably go 
with '_func' as the keyword.

~Ethan~

PS
Thanks for the code review!

[toc] | [prev] | [next] | [standalone]


#8597

FromDuncan Booth <duncan.booth@invalid.invalid>
Date2011-06-30 09:00 +0000
Message-ID<Xns9F1465CB4AC62duncanbooth@127.0.0.1>
In reply to#8540
Lie Ryan <lie.1296@gmail.com> wrote:

> Simplicity is one, using @decor() means you have at least three-level
> nested functions, which means the code is likely to be very huge and
> perhaps unnecessarily.
> 

If you don't like the extra level of function nesting that you get from 
returning a decorator factory instead of a decorator then you can factor 
out the complexity by using a decorator.

---------------------------------------------
from functools import wraps

def decorator_with_args(deco):
    """Wrap a decorator so that it can take arguments"""
    @wraps(deco)
    def wrapper(*args, **kw):
        @wraps(deco)
        def inner(f):
            return deco(f, *args, **kw)
        return inner
    return wrapper

@decorator_with_args
def repeat(f, count=2):
    """Decorator which calls the decorated function <count> times"""
    @wraps(f)
    def wrapper(*args, **kw):
        return [f(*args, **kw) for i in range(count)]
    return wrapper

@repeat(3)
def ticker(msg):
    print("tick", msg)

ticker("tock")
-----------------------------------------
decorator_with_args is a decorator factory that creates a decorator 
factory from a decorator and passes its arguments through to the 
underlying decorator.

-- 
Duncan Booth http://kupuguy.blogspot.com

[toc] | [prev] | [next] | [standalone]


#8635

FromJohn Posner <jjposner@codicesoftware.com>
Date2011-07-01 11:18 -0400
Message-ID<mailman.542.1309535310.1164.python-list@python.org>
In reply to#8540
On 2:59 PM, Ethan Furman wrote:

<snip>

>     def __call__(self, func=None):
>         if func is None:
>             return self._call()
>         self.func = func
>         return self
>     def _call(self):
>         print("\n" + self.char * 50)
>         self.func()
>         print(self.char * 50 + '\n')
>

I believe the "if" block should be:

  if func is None:
      self._call()
      return

Or perhaps the _call() method should be revised:

  def _call(self):
      print("\n" + self.char * 50)
      retval = self.func()
      print(self.char * 50 + '\n')
      return retval

-John

[toc] | [prev] | [next] | [standalone]


#8541

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-06-28 11:20 -0600
Message-ID<mailman.482.1309281662.1164.python-list@python.org>
In reply to#8539
On Tue, Jun 28, 2011 at 10:52 AM, Jigar Tanna
<poisonousrattle5@gmail.com> wrote:
> coming across to certain views from people, it is not a good practice
> to use
> decorators with arguments (i.e. @memoize() ) and instead it is good to
> just
> use @memoize. Can any of you guys explain me advantages and
> disadvantages of
> using each of them

The main concern I think is not with how the decorators are used but
how they are designed.  An argument-less decorator will normally be
used as @memoize, and @memoize() will likely not work.  A decorator
with arguments that are all optional will normally be used as
@memoize(), and @memoize will likely not work.  This naturally leads
to some confusion: do I need parentheses to use this particular
decorator or not?

As a personal design goal I try to make my decorators either take at
least one required argument or take no arguments at all.  This way
it's either @memoize or @memoize(foo), but never just the confusing
@memoize().

Cheers,
Ian

[toc] | [prev] | [next] | [standalone]


#8554

FromBen Finney <ben+python@benfinney.id.au>
Date2011-06-29 08:19 +1000
Message-ID<87hb79mweh.fsf@benfinney.id.au>
In reply to#8539
Jigar Tanna <poisonousrattle5@gmail.com> writes:

> where I came across a special case of using arguments with decorators

A decorator is a function which takes exactly one parameter, and returns
a function based on that parameter.

    <URL:http://docs.python.org/glossary.html#term-decorator>
    <URL:http://docs.python.org/reference/compound_stmts.html#function>

So a decorator always takes an argument: the function to be decorated.

> Below is the code for memoization where I was looking at the
> concept...

[…]

> def memoize(get_key=get_key, cache=cache) :
>     def _memoize( function) :
>         print function
>         def __memoize(*args, **kw) :
>             key = get_key(function, *args, **kw)
>             try:
>                 return cache[key]
>             except KeyError:
>                 cache[key] = function( *args, **kw)
>                 return cache[key]
>         return __memoize
>     return _memoize

Note that the function ‘memoize’ is not a decorator. It's a decorator
creator; it returns a decorator.

That is, you pass this function zero, one, or two arguments, and it
defines a new function with the name ‘_memoize’; *that* function is then
returned. That function is the decorator, and will receive exactly one
parameter when you decorate a function.

> @memoize()

‘memoize()’ calls the ‘memoize’ function, which creates and returns the
decorator. That return value is then used (because of the ‘@’ syntax) to
decorate the function whose definition follows.

> def factory(n) :
>     return n * n

This function is created, and then immediately passed as the single
argument to the decorator that ‘memoize()’ returned above. The return
value from that decorator then gets bound to the name ‘factory’.

(Side note: it's unfortunate the writer of ‘memoize’ didn't wrap the
decorated function better. In this implementation, the resulting
function will be bound to the name ‘factory’, but will consider its name
to be ‘_memoize’. It will also lack the docstring of the ‘factory’
function. For a better way, see ‘functools.wraps’.)

> coming across to certain views from people, it is not a good practice
> to use decorators with arguments (i.e. @memoize() )

Correcting your mental model: those are not decorators. They are
creating a decorator, by calling a function that returns a decorator.

And if you've come across views that say there's something wrong with
that, those views are mistaken. There are many good reasons to call a
function to return a decorator, and “I get a different decorator
depending on what arguments I give to this decorator-creator” is the
pattern.

> and instead it is good to just use @memoize.

That wouldn't work in this case, of course, since the function ‘memoize’
is not a decorator: it doesn't take the function-to-be-decorated as an
argument.

In other cases where the decorator is available immediately as a defined
function (e.g. ‘functools.partial’ or the built-in ‘property’), there is
of course no problem using that.

But it's not always the case that you have that decorator yet, and
calling another function (with whatever arguments it needs) to create
the decorator at the point where you need to use it is also fine.

> Can any of you guys explain me advantages and disadvantages of using
> each of them

I hope that explains.

-- 
 \     “Men never do evil so completely and cheerfully as when they do |
  `\        it from religious conviction.” —Blaise Pascal (1623–1662), |
_o__)                                                   Pensées, #894. |
Ben Finney

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web