From: Stephen Hansen on
Okay, so!

I actually never quite got around to learning to do deep and useful
magic with decorators. I've only ever done the most basic things with
them. Its all been a little fuzzy in my head: things like what order
decorators end up being called in if there's more then one, etc.

But in my current situation, what I'm wanting to do is have a decorator
that wraps a function but which takes an *optional* argument, and sets
that argument as an attribute on said function if its there.

Here's what some tweaking and playing around has gotten me, as a recipe:

import functools

def my_decorator(arg):
if callable(arg): # Stuck on 2.5. Leavemealone. :)
protocol = None
else:
protocol = arg

def wrap(fn):
print "Wrapping."
fn.protocol = protocol

@functools.wraps(fn)
def wrapper(*args, **kwargs):
print "Calling."
result = fn(*args, **kwargs)
print "Called."
return result

return wrapper

if not protocol: # argument-less decorator
print "Calling wrap."
return wrap(arg)
else:
print "Returning wrap."
return wrap

To be used as:

class Thing(object):
@expose
def test1(self, arg1):
return arg1

@expose("testing")
def test2(self, arg2):
return arg2

So, my question: am I doing this right? :) Some play-through testing
appears to work. But, the dizzying array of nested def's up there leaves
me a bit dazed, so I'm wondering if there's a simpler way to accomplish
what I'm trying to do.

Thanks.

--

... Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/

From: Thomas Jollans on
On 07/02/2010 07:41 PM, Stephen Hansen wrote:
> Okay, so!
>
> I actually never quite got around to learning to do deep and useful
> magic with decorators. I've only ever done the most basic things with
> them. Its all been a little fuzzy in my head: things like what order
> decorators end up being called in if there's more then one, etc.
>
> But in my current situation, what I'm wanting to do is have a decorator
> that wraps a function but which takes an *optional* argument, and sets
> that argument as an attribute on said function if its there.
>
> Here's what some tweaking and playing around has gotten me, as a recipe:
>
> import functools
>
> def my_decorator(arg):
> if callable(arg): # Stuck on 2.5. Leavemealone. :)
> protocol = None
> else:
> protocol = arg
>
> def wrap(fn):
> print "Wrapping."
> fn.protocol = protocol
>
> @functools.wraps(fn)
> def wrapper(*args, **kwargs):
> print "Calling."
> result = fn(*args, **kwargs)
> print "Called."
> return result
>
> return wrapper
>
> if not protocol: # argument-less decorator
> print "Calling wrap."
> return wrap(arg)
> else:
> print "Returning wrap."
> return wrap

Looks good! You may still want to use functools.update_wrapper or
functools.wraps on "wrap".

PS: if you weren't stuck on 2.5, but were using 3.x, there's all kinds
of fun stuff you could do with function annotations ;-)

>
> To be used as:
>
> class Thing(object):
> @expose
> def test1(self, arg1):
> return arg1
>
> @expose("testing")
> def test2(self, arg2):
> return arg2
>
> So, my question: am I doing this right? :) Some play-through testing
> appears to work. But, the dizzying array of nested def's up there leaves
> me a bit dazed, so I'm wondering if there's a simpler way to accomplish
> what I'm trying to do.
>
> Thanks.
>

From: Alf P. Steinbach /Usenet on
* Stephen Hansen, on 02.07.2010 19:41:
> Okay, so!
>
> I actually never quite got around to learning to do deep and useful
> magic with decorators. I've only ever done the most basic things with
> them. Its all been a little fuzzy in my head: things like what order
> decorators end up being called in if there's more then one, etc.
>
> But in my current situation, what I'm wanting to do is have a decorator
> that wraps a function but which takes an *optional* argument, and sets
> that argument as an attribute on said function if its there.
>
> Here's what some tweaking and playing around has gotten me, as a recipe:
>
> import functools
>
> def my_decorator(arg):
> if callable(arg): # Stuck on 2.5. Leavemealone. :)
> protocol = None
> else:
> protocol = arg
>
> def wrap(fn):
> print "Wrapping."
> fn.protocol = protocol
>
> @functools.wraps(fn)
> def wrapper(*args, **kwargs):
> print "Calling."
> result = fn(*args, **kwargs)
> print "Called."
> return result
>
> return wrapper
>
> if not protocol: # argument-less decorator
> print "Calling wrap."
> return wrap(arg)
> else:
> print "Returning wrap."
> return wrap
>
> To be used as:
>
> class Thing(object):
> @expose
> def test1(self, arg1):
> return arg1
>
> @expose("testing")
> def test2(self, arg2):
> return arg2
>
> So, my question: am I doing this right? :) Some play-through testing
> appears to work. But, the dizzying array of nested def's up there leaves
> me a bit dazed, so I'm wondering if there's a simpler way to accomplish
> what I'm trying to do.

If you're willing to have slightly more explicit usage code, consider e.g.


<code>
#Py3

import functools

class expose:
def __init__( self, protocol = None ):
self._protocol = protocol

def __call__( self, f ):
print( "Wrapping." )
f.protocol = self._protocol

@functools.wraps( f )
def wrapper( *args, **kwargs ):
print( "Calling." )
result = f( *args, **kwargs )
print( "Called." )
return result

return wrapper

class Thing(object):
@expose()
def test1(self, arg1):
return arg1

@expose( "testing" )
def test2(self, arg2):
return arg2

o = Thing()
print( o.test1( 1.11 ) )
print( o.test2( 2.22 ) )
</code>


Cheers & hth.,

- Alf


--
blog at <url: http://alfps.wordpress.com>
From: Carl Banks on
On Jul 2, 10:41 am, Stephen Hansen <me+list/pyt...(a)ixokai.io> wrote:
> Okay, so!
>
> I actually never quite got around to learning to do deep and useful
> magic with decorators. I've only ever done the most basic things with
> them. Its all been a little fuzzy in my head: things like what order
> decorators end up being called in if there's more then one, etc.
>
> But in my current situation, what I'm wanting to do is have a decorator
> that wraps a function but which takes an *optional* argument, and sets
> that argument as an attribute on said function if its there.
>
> Here's what some tweaking and playing around has gotten me, as a recipe:
>
>      import functools
>
>      def my_decorator(arg):
>          if callable(arg): # Stuck on 2.5. Leavemealone. :)
>              protocol = None
>          else:
>              protocol = arg
>
>          def wrap(fn):
>              print "Wrapping."
>              fn.protocol = protocol
>
>              @functools.wraps(fn)
>              def wrapper(*args, **kwargs):
>                  print "Calling."
>                  result = fn(*args, **kwargs)
>                  print "Called."
>                  return result
>
>              return wrapper
>
>          if not protocol: # argument-less decorator
>              print "Calling wrap."
>              return wrap(arg)
>          else:
>              print "Returning wrap."
>              return wrap
>
> To be used as:
>
>      class Thing(object):
>          @expose
>          def test1(self, arg1):
>              return arg1
>
>          @expose("testing")
>          def test2(self, arg2):
>              return arg2
>
> So, my question: am I doing this right? :) Some play-through testing
> appears to work. But, the dizzying array of nested def's up there leaves
> me a bit dazed, so I'm wondering if there's a simpler way to accomplish
> what I'm trying to do.


I usually recommend to factoring out magic parts into their own little
function, which then invoke the non-magical part in different ways:


def expose_as(protocol):
def wrap(fn):
print "Wrapping."
fn.protocol = protocol

@functools.wraps(fn)
def wrapper(*args, **kwargs):
print "Calling."
result = fn(*args, **kwargs)
print "Called."
return result

return wrapper

return wrap


def expose(arg):
"""Magic function to allow omitting argument."""
if type(arg) is types.FunctionType:
print "Calling with arg as function"
return expose_as(None)(arg)
print "Calling with arg as protocol"
return expose_as(arg)


I believe it's a good practice to isolate magic so that it's easy to
see, and also to ensure there's a non-magical way to do it if that is
needed. However, the world won't come to an end if you do it your
way.


Carl Banks
From: Stephen Hansen on
On 7/2/10 11:55 AM, Thomas Jollans wrote:
> Looks good! You may still want to use functools.update_wrapper or
> functools.wraps on "wrap".

Are you sure? I've been doing a little bit of experimentation and I only
did the 'wraps' on that inner function, because it seemed that it was
all that was needed to get the propagation of function data I expected.

I've since changed it to:

def wrapper(fn, *args, **kwargs):
print "Calling."
result = fn(*args, **kwargs)
print "Called."
return result

return decorator.decorator(wrapper, fn)

Because the decorator library includes argspec propagation which was
important for other parts of this code which does introspection. (This
toy project is, I fully admit, going into very dark and evil places that
I have at length advised people against, 'You don't want to do that!').

> PS: if you weren't stuck on 2.5, but were using 3.x, there's all kinds
> of fun stuff you could do with function annotations ;-)

Oh, believe me, I desperately wish I could go to 3.x. Between function
annotations and the new metaclass power (Hi, __prepare__, I love you,
please wait for me and I'll marry you when I migrate).

I just can't yet.

I can't quite figure out why people are all, '3.x brings you nothing'.
Yes, the majority of the focus was cleaning, removing edges, but *lord*
there's a LOT I am VERY looking forward to using.

--

Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/