Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #8539 > unrolled thread
| Started by | Jigar Tanna <poisonousrattle5@gmail.com> |
|---|---|
| First post | 2011-06-28 09:52 -0700 |
| Last post | 2011-06-29 08:19 +1000 |
| Articles | 12 — 7 participants |
Back to article view | Back to comp.lang.python
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
| From | Jigar Tanna <poisonousrattle5@gmail.com> |
|---|---|
| Date | 2011-06-28 09:52 -0700 |
| Subject | Using 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]
| From | Lie Ryan <lie.1296@gmail.com> |
|---|---|
| Date | 2011-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]
| From | John Posner <jjposner@codicesoftware.com> |
|---|---|
| Date | 2011-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]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2011-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2011-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]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2011-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2011-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]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2011-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]
| From | Duncan Booth <duncan.booth@invalid.invalid> |
|---|---|
| Date | 2011-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]
| From | John Posner <jjposner@codicesoftware.com> |
|---|---|
| Date | 2011-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2011-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]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2011-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