By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
459,954 Members | 1,592 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 459,954 IT Pros & Developers. It's quick & easy.

decorator peculiarity

P: n/a
Hi,

I just wrote my first decorator example - and I already love them.

However I've encountered one peculiarity that strikes me odd:

When one writes a decorated function like this:

@decorate
def foo():
pass

the function decorate usually looks like this:

def decorate(func):
def _d(*args, **kwargs):
do_something()
# call func
func(*args, **kwargs)
return _d

So the function decorator has to return a function that is bound to the name
foo in the originating context (module or class) and gets the function
passed as argument.

But if I want my decorator to be parametrized, it looks like this:

@decorate(arg)
def foo():
pass

def decorate(arg):
def f(func):
def _d(*args, **kwargs):
do_something(arg)
# call func
func(*args, **kwargs)
return _d

So what happens is that an decorater with arguments is called with these,
returns a callable that then is called with foo, and the result is stored
under foo.

Now why this layer of indirection? I would have supposed that things look
like this:
def decorate(func, arg):
def _d(*args, **kwargs):
do_something(arg)
# call func
func(*args, **kwargs)
return _d

A sideeffect of this behaviour is that for a fully keyword-argumentized
function I still have to write parentheses:

@decorate()
def foo():
pass

def decorate(arg=None):
def f(func):
def _d(*args, **kwargs):
do_something(arg)
# call func
func(*args, **kwargs)
return _d

I don't mind the extra parentheses too much - it just made me wonder what
the rationale behind this is.

--
Regards,

Diez B. Roggisch
Jul 18 '05 #1
Share this Question
Share on Google+
3 Replies


P: n/a
Diez B. Roggisch wrote:
def decorate(func):
def _d(*args, **kwargs):
do_something()
# call func
func(*args, **kwargs)
return _d
@decorate
def foo():
pass
[T]he function decorator has to return a function that is bound to the name
foo in the originating context (module or class) and gets the function
passed as argument. If I want my decorator ... parametrized...:
@decorate(arg)
def foo():
pass
def decorate(arg):
def f(func):
def _d(*args, **kwargs):
do_something(arg)
# call func
func(*args, **kwargs)
return _d
Now why this layer of indirection?


Let's rewrite your example a bit, it may become obvious.
def decorate(func):
def replacement(argspec):
do_something()
return func(argspec)
return replacement
@decorate
def foo(argspec): pass
---
def decorate2():
def _decorate(func):
def replacement(argspec):
do_something()
return func(argspec)
return replacement
return _decorate
@decorate()
def foo(argspec): pass
---
def decorate3(arg):
def _decorate(func):
def replacement(argspec):
do_something()
return func(argspec)
return replacement
return _decorate
@decorate3(arg)
def foo(argspec): pass
---
Clear now? there is no extra indirection. If you call something on
the @ line, the _result_of_that_call_ should be a decorator function.
If you use my curry recipe in the python cookbook, you can use curry
lower the apparent "indirection":

def decorate4(arg, func):
def replacement(argspec):
do_something()
return func(argspec)
return replacement
@curry(decorate4, arg)
def foo(argspec): pass

By the way, I _do_ think curry is an appropriate name. The Curry-Howard
isomorphism shows the equivalence of a functions on tuples with single-
argument functions returning functions. In a functional language, where
there is no distinction made between a a function with no arg and its
result, the transformation is clear. With a language like Python, which
pays attention to when a function is invoked, you are better off
returning a function which can take the remaining arguments, and
controlling when the final function is called by returning a function
that can take all the remaining arguments at once.

-Scott David Daniels
Sc***********@Acm.Org
Jul 18 '05 #2

P: n/a
Hi
Clear now? there is no extra indirection.**If*you*call*something*on
the @ line, the _result_of_that_call_ should be a decorator function.
If you use my curry recipe in the python cookbook, you can use curry
lower the apparent "indirection":


Ok - that makes it clear, thanks.

--
Regards,

Diez B. Roggisch
Jul 18 '05 #3

P: n/a
Diez B. Roggisch wrote:
However I've encountered one peculiarity that strikes me odd:

When one writes a decorated function like this:

@decorate
def foo():
pass

the function decorate usually looks like this:

def decorate(func):
def _d(*args, **kwargs):
do_something()
# call func
func(*args, **kwargs)
return _d

So the function decorator has to return a function that is bound to the name
foo in the originating context (module or class) and gets the function
passed as argument.

But if I want my decorator to be parametrized, it looks like this:

@decorate(arg)
def foo():
pass

def decorate(arg):
def f(func):
def _d(*args, **kwargs):
do_something(arg)
# call func
func(*args, **kwargs)
return _d

So what happens is that an decorater with arguments is called with these,
returns a callable that then is called with foo, and the result is stored
under foo.


the decorator itself must be a callable, so to create parameterized decorators,
you have to create a callable that returns a callable.

due to some weird obsession with nested functions and lexical scoping, most
decorator examples use functions inside functions inside functions, but you can
of course use a good old class instead:

class decorator:
def __init__(self, params):
# save the parameters
def __call__(self, func):
# decorate the function
return func

@decorator(params)
def func(...):
pass

or even:

class decorator:
def __init__(self, params):
# save the parameters
def __call__(self, func):
self.func = func
return self.handler
def handler(self, *args):
# do something
self.func(*args)
# do something

</F>

Jul 18 '05 #4

This discussion thread is closed

Replies have been disabled for this discussion.