Am 08.12.2011 12:43 schrieb Chris Angelico:
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