Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constants and sugar

I have a set of functions that I use very often, so I would like to collect them in a library. Before I start writing the library, I was thinking about where to store the constants that influence the behavior of some of the functions.

What I would like to write when using the library was the following:

import tools
tools.collect(object_a, object_b, mode=tools.collect.RECURSIVE)

Generally speaking, the constants the function should be able to accept should be stored in the function itself.

In order to achieve this, I created a decorator function that assigns the passed attributes to the decorated function.

def attr_decorator(**attrs):
    def decorator(f):
        for k, v in attrs.iteritems():
            setattr(f, k, v)
        return f
    return decorator

This decorator could be used like this:

@attr_decorator(
    FLAT = 1 << 0,
    RECURSIVE 1 << 1,
)
def collect(a, b, mode):
    # ...

This works pretty fine so far.

But what about default arguments?

@attr_decorator(
    FLAT = 1 << 0,
    RECURSIVE 1 << 1,
)
def collect(a, b, mode=collect.RECURSIVE):
    # ...

This does not work, because the collect function is not defined (and therefore not even decorated) at the point the default-value for the mode argument is stored.

This does not look nice

The only solution I was able to come up with resulted in an awkward syntax and it just didn't look nice. I am giving the decorator-function the same attributes as the function that is going to be decorated.

def attr_decorator(**attrs):
    def decorator(f):
        for k, v in attrs.iteritems():
            setattr(f, k, v)
        return f

    for k, v in attrs.iteritems():
        setattr(decorator, k, v)

    return decorator

It doesn't take a genius to recognize that this is not nice to read:

collect_d = attr_decorator(
    FLAT = 1 << 0,
    RECURSIVE = 1 << 1,
)
@collect_d
def collect(root, callback, mode=collect_d.RECURSIVE):
    # ...

Question:

Can you think of a better approach? I would really like to stay with the "one-statement-to-decorate" thing.

like image 726
Niklas R Avatar asked Nov 13 '22 21:11

Niklas R


1 Answers

You could use a special variable as a reference to the function being defined.

class Attr(object):
    def __init__(self, name): self.name = name

class Attributor(object):
    def __getattr__(self, x): return Attr(x)

_ = Attributor()

def attr_decorator(**attrs):
    def decorator(f):
        for k, v in attrs.iteritems():
            setattr(f, k, v)
        f.func_defaults = tuple(attrs[t.name] if isinstance(t, Attr) else t for t in f.func_defaults)
        return f
    return decorator    

@attr_decorator(
    FLAT = 1 << 0,
    RECURSIVE = 1 << 1,
)
def collect(a, b, mode=_.RECURSIVE, foo=123):
    print a, b, mode, foo

collect(100,200) # 100 200 2 123
like image 127
georg Avatar answered Nov 15 '22 12:11

georg