Am 08.12.2011 12:43 schrieb Chris Angelico:
On Thu, Dec 8, 2011 at 10:22 PM, K.-Michael Aye<kmichael....@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
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to