Decorators, with optional arguments

S

Stephen Hansen

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/
 
A

Alf P. Steinbach /Usenet

* 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
 
C

Carl Banks

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
 
S

Stephen Hansen

<code>
#Py3

I'm stuck on Python 2.x, as I mentioned (albeit only in a comment). That
said this code does not seem to be including any Py3isms that aren't
compatible.
class Thing(object):
@expose()
def test1(self, arg1):
return arg1

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

Its the @expose() which bugs me. I have some decorators (made by me and
others) which have no args, that look like @hello. Then some which
always have args, that look like @hello("whatsup dude"). To then have a
decorator which has no args as @hello(), I can't abide. For internal
consistancy purposes, I want "no arguments" to always look like @hello,
and "arguments" to look like @hello(something). I don't want to have to
think, "Hey, does this no-arg version require parens or no?"

--

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


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.10 (Darwin)

iQEcBAEBAgAGBQJMLsx8AAoJEKcbwptVWx/lANoH/2oICyhabUhIW0YpStuR1faR
GMV2dpZDc81S1UFXmR+jbzM8nHIS3kRZu9A1dZ3Z0ikuuZhdocr4Kr958BBqzVYg
r3/1d8nBfoj0DDhPPUNJdh5h1oiHbpa4jGNAI7wHT4tHIYTaprGeAG+dlvUT152T
3n452hrCIXLSyFtaR8EDz9Z6SZW76HryreK7FsKFx2qEP3PKQqk52acL+DXDxPbq
se2z9xYmwj49JBBTlY0yngIQrqtW1hwh9t9g/rq7dA5eJM1WmVnVtEFIzsi6bRYU
ji9TWLRwoK88kwvrsED+mV2/tlkC1Bg0DQXhw+roUcEWHxktGJWE7PQIsw5S+j8=
=AUfP
-----END PGP SIGNATURE-----
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,060
Latest member
BuyKetozenseACV

Latest Threads

Top