2

How can I define a decorator decor to make this work? There are a lot of posts about this but I'm still not finding a solution. Maybe it is a very bad idea.

Example 1, write a function that is broken until the decorator is applied:

defaults = {'a': 1, 'b': 2}

@decor(defaults)
def f(x):
    print(x, a, b)

Example 2:

defaults = {'a': 1, 'b': 2}

@decor(defaults)
def fun(x):
   print(locals())

f(3)

Should give

{'b': 2, 'x': 3, 'a': 1}

Or maybe to be clearer, I want the signature to be modified by the decorator so that help(f) gives

Help on function f in module __main__:

f(x, a=1, b=2)
Stefan van den Akker
  • 6,661
  • 7
  • 48
  • 63
mathtick
  • 6,487
  • 13
  • 56
  • 101
  • 2
    Don't! Modifying the *local* variables dynamically is something that's really wrong, and it's actually really hard to implement reliably without introducing serious security risks in your programs. If you gave some context you'll probably find out that there are 3-4 better ways to achieve what you want, without doing this. – Bakuriu Jul 05 '13 at 16:29
  • 2
    You *cannot* do this without rewriting the `f` byte code. You **really** don't want to go there. What are you really trying to solve? – Martijn Pieters Jul 05 '13 at 16:31
  • I don't care about the local variables ... that was just to print something. I just want to programmatically control the function signature (with defaults). – mathtick Jul 05 '13 at 16:41
  • This makes it seem like modifying function signatures is bad: http://stackoverflow.com/questions/1409295/set-function-signature-in-python ... I just was thinking it would be nice to fill out a function kwargs automatically as an alternative to passing around a special dictionary (or other object) containing shared values, or using something global. – mathtick Jul 05 '13 at 17:00
  • 1
    Not a duplicate, but this may help: http://stackoverflow.com/questions/5929107/python-decorators-with-parameters – Markon Jul 05 '13 at 17:03
  • 2
    More fundamentally, how would you even use this? Your decorated function would have to refer to the "default" variables to be able to even use them. Given that, using this decorator will make your code *much* harder to read. Don't do it. – Marcin Jul 05 '13 at 17:15
  • @Marcin, yes that is exactly what I was trying to do (see the new Example 1 in the OP) and it is probably a bad idea. I was just looking for a way to stay functional yet pass a bunch of kwargs with defaults around to various function AND have the functions have a useful signature in the "help". – mathtick Jul 05 '13 at 17:24
  • @mathtick What do you mean, "stay functional"? How is having free variables functional? – Marcin Jul 05 '13 at 17:40
  • @Marcin, good question. What I mean is very little: just that scoping is loosely respected from the point of view of reading and understanding a small piece of code. Looking at the help(f) output should tell me that the function takes three inputs, x (with no default), and a and b which have integer defaults. But then I am breaking the readability by trying to type less ... so what I am asking for is by it's nature a bad idea. Was just trying to think of a nice way to avoid maintaining long-signatured functions. – mathtick Jul 05 '13 at 17:56
  • 1
    @mathtick This would be a way to make maintenance much harder. – Marcin Jul 05 '13 at 17:57

2 Answers2

1

Ok, you've been already warned that this is insecure and stuff, but just for fun, this decorator must work regardless of whether decorated function receives kwargs or not

import inspect as insp

def modify(f, defaults):
    lines = insp.getsourcelines(f)[0]
    lines.pop(0) # remove decorator heading
    for k, v in defaults.items():
        lines.insert(1, '    %s = %s\n' % (k, v))
    exec ''.join(lines)
    return locals()[f.__name__]

def decor(defaults):
    def wrap(f):
        def wrapped_f(*args):
            new_f = modify(f, defaults)
            return new_f(*args)
        return wrapped_f
    return wrap
dmytro
  • 1,293
  • 9
  • 21
  • 1
    wrote something similar :) https://github.com/shaung/automagically/blob/master/tests/tests.py – Shaung Jul 05 '13 at 17:17
  • Haha, this looks dangerous. I'm refining the question a bit. Gradually getting closer to convincing myself that it is a very bad idea. – mathtick Jul 05 '13 at 17:19
  • Ok, I corrected the code, now it works as a decorator :) @Shaung, the magic is cool, but if I'm not mistaken it requires byteplay, which is not in stdlib – dmytro Jul 05 '13 at 17:32
0

Not sure if this is what you really wanted, but here goes

defaults = {'a': 1, 'b': 2}

def decor(**kw_dec):
    def decorate(func):
        def ret(*args, **kwargs):
            func(*args, **dict(kw_dec, **kwargs))
        return ret
    return decorate


def fun(*args, **kwargs):
    print(locals())

fun = decor(**defaults)(fun)
fun(3)

Gives

{'args': (3,), 'kwargs': {'a': 1, 'b': 2}}
ersran9
  • 968
  • 1
  • 8
  • 13
  • Ideally "decor" should work with any function. This requires fun to have kwargs and the signature is not really preserved as far as I can tell. – mathtick Jul 05 '13 at 17:06