I love the decorator in Python!!!

Thomas Rachel nutznetz-0c1b6768-bfa9-48d5-a470-7603bd3aa915 at spamschutz.glglgl.de
Fri Dec 9 04:57:59 EST 2011


Am 08.12.2011 12:43 schrieb Chris Angelico:
> On Thu, Dec 8, 2011 at 10:22 PM, K.-Michael Aye<kmichael.aye at gmail.com>  wrote:
>> I am still perplexed about decorators though, am happily using Python for
>> many years without them, but maybe i am missing something?
>> For example in the above case, if I want the names attached to each other
>> with a comma, why wouldn't I just create a function doing exactly this? Why
>> would I first write a single name generator and then decorate it so that I
>> never can get single names anymore (this is the case, isn't it? Once
>> decorated, I can not get the original behaviour of the function anymore.
>
> The example given is a toy. It's hardly useful.

Right. It was supposed to be an example.

In my case, I work with a script used to build a XML file. I change this 
script from time to time in order to match the requirements.
Here I find it useful just to add some more yield statements for adding 
entries.

But now that I think again about it, it's more an example for 
generators, not so much for decorators - which I like as well.

*****

But some useful examples for decorators include

1. "Threadifying" a function, i.e. turning it into a Thread object, or 
into a callable which in turn starts a thread according to the given 
parameters.

2. Automatically calling a function if the given module is executed as a 
script, a kind of replacement for the "if __name__ == '__main__':" stuff.

3. Meta decorators:

I find it annoying to have to wrap the function given to the decorator 
into another one, modifying its properties and returning that in turn.

def wrapfunction(decorated):
     """Wrap a function taking (f, *a, **k) and replace it with a
     function taking (f) and returning a function taking (*a, **k) which
     calls our decorated function.
     """
     from functools import wraps
     @wraps(decorated)
     def wrapped_outer(f):
         @wraps(f)
         def wrapped_inner(*a, **k):
             return decorated(f, *a, **k)
         return wrapped_inner
     return wrapped_outer

makes it much easier to create decorators which just wrap a function 
into another, extending its funtionality:

@wrapfunction
def return_list(f, *a, **k)
     return list(f(*a, **k))

is much easier and IMHO much better to read than

def return_list(f):
     """Wrap a function taking (f, *a, **k) and replace it with a
     function taking (f) and returning a function taking (*a, **k) which
     calls our decorated function.
     """
     from functools import wraps
     @wraps(f)
     def wrapped(*a, **k):
         return list(f, *a, **k)
     return wrapped

- especially if used multiple times.

3a. This is a modified case of my first example: If you want a function 
to assemble and return a list instead of a generator object, but prefer 
"yield" over "ret=[]; ret.append();...", you can do that with this 
@return_list.

4. So-called "indirect decorators":

@spam(eggs)
def foo(bar):
     pass

are as well quite tricky to build when taking

def indirdeco(ind):
     from functools import update_wrapper, wraps
     upd=wraps(ind)
     # outer wrapper: replaces a call with *a, **k with an updated
     # lambda, getting the function to be wrapped and applying it and
     # *a, **k to ind.
     outerwrapper=lambda *a, **k: upd(lambda f: ind(f, *a, **k))
     # We update this as well:
     return upd(outerwrapper)
     # We don't update f nor the result of ind() - it is the callee's
     # business.

It is kind of reverse to 3.

@indirdeco
def addingdeco(f, offset):
     return lambda *a, **k: f(*a, **k) + offset
     # Here should maybe be wrapped - it is just supposed to be an
     # example.

5. Creating a __all__ for a module. Instead of maintaining it somewhere 
centrally, you can take a

class AllList(list):
     """list which can be called in order to be used as a __all__-adding
     decorator"""
     def __call__(self, obj):
         """for decorators"""
         self.append(obj.__name__)
         return obj

, do a __all__ = AllList()

and subsequently decorate each function with

@__all__

6. Re-use a generator:

A generator object is creted upon calling the generator function with 
parameters and can be used only once. A object wrapping this generator 
might be useful.

# Turn a generator into a iterable object calling the generator.
class GeneratorIterable(object):
     """Take a parameterless generator function and call it on every
     iteration."""
     def __init__(self, gen):
         # Set object attribute.
         self.gen = gen
     def __iter__(self):
         # Class attribute calls object attribute in order to keep
         # namespace variety small.
         return self.gen()


@GeneratorIterable
def mygen():
     yield 1
     yield 2

list(mygen) -> [1, 2]
list(mygen) -> [1, 2] # again, without the ()

Might be useful if the object is to be transferred to somewhere else.

*****

Some of these decorators are more useful, some less if seen standalone, 
but very handy if creating other decorators.

HTH nevertheless,


Thomas



More information about the Python-list mailing list