0

I have the following function:

def f(x, y):
    print('x: %s' % x)
    print('y: %s' % y)

And I would like to have a wrapper like:

def g(**kwargs):
    print('hello')
    f(**kwargs)
    print('world')

Wrapper g is the function that will be returned by the decorator, so it can invoke not exactly f function but rather any function with any arguments list, so this is important to provide to function f **kwargs.

After defining g function, I would like to return g function, but with arguments list copied from f function and with the name of f function. I tried this one below:

import types

f_code = f.__code__
g_code = g.__code__
g.__code__ = types.CodeType(
    f_code.co_argcount, g_code.co_nlocals,
    g_code.co_stacksize, g_code.co_flags,
    g_code.co_code, g_code.co_consts, g_code.co_names,
    f_code.co_varnames, g_code.co_filename, f_code.co_name,
    g_code.co_firstlineno, g_code.co_lnotab,
    g_code.co_freevars, g_code.co_cellvars
)

But after calling g function I got the segmentation fault.

I googled how to solve this problem in a higher-level way, but no solutions were satisfied for me. I tried @functools.wraps, but it doesn't change arguments list, and I tried @decorator.decorator but it didn't call decorator function at all. Maybe you know better solutions for this problem?

UPDATE

Basically there is more extended example below:

funcs = {}

def h(fi, *args, **kwargs):
    def g(*args, **kwargs):
        print('hello')
        result = fi(**kwargs)
        print('world')
        return result
    funcs[fi.func_code.co_name] = g
    return g

@h
def f(x, y):
    print('%s %s' % (x, y))
    return 5

# z index is unneeded, so should be filtered
extended_arguments = {'x': 1, 'y': 2, 'z': 3}

func = funcs['f']
func_code = func.func_code
arguments = {
    i: x for i, x in extended_arguments.items()
    if x in func_code.co_varnames[:func_code.co_argcount]
}
func(**arguments)

Running this one fails with error below:

hello
Traceback (most recent call last):
  File "x.py", line 26, in <module>
    func(**arguments)
  File "x.py", line 6, in g
    result = fi(**kwargs)
TypeError: f() takes exactly 2 arguments (0 given)

This is exact case I would like to implement.

Attempt with exec:

funcs = {}

def h(fi, *args, **kwargs):
    fi_code = fi.func_code
    exec(
        '''
def g(%s*args, **kwargs):
    print('hello')
    result = fi(**kwargs)
    print('world')
    return result
        ''' % ''.join(
            '%s, ' % varname
            for varname in fi_code.co_varnames[:fi_code.co_argcount]
        )
    )
    funcs[fi_code.co_name] = g
    return g

@h
def f(x, y):
    print('%s %s' % (x, y))
    return 5

# z index is unneeded, so should be filtered
extended_arguments = {'x': 1, 'y': 2, 'z': 3}

func = funcs['f']
func_code = func.func_code
arguments = {
    i: x for i, x in extended_arguments.items()
    if i in func_code.co_varnames[:func_code.co_argcount]
}
func(**arguments)

Produces the following error:

hello
Traceback (most recent call last):
  File "y.py", line 34, in <module>
    func(**arguments)
  File "<string>", line 4, in g
NameError: global name 'fi' is not defined
pt12lol
  • 2,332
  • 1
  • 22
  • 48
  • 1
    The various attributes of a codeobject are expected to be consistent with one another. You can't just change some of these from another function and have the Python interpreter not fail when those expectations are not met; in this case you have paired bytecode that expects variable arguments with an argcount that doesn't match that expectation. It'll be much easier to generate a shim function with `exec()` than to make a consistent `CodeType` instance. – Martijn Pieters Oct 31 '16 at 15:32
  • 1
    But first of all: why do you think you need this? What tools are expecting to find non-variable arguments (and you'd have to use `*args` in addition to `**kwargs` for this to work correctly). – Martijn Pieters Oct 31 '16 at 15:34
  • (Older) example of building a façade function: https://github.com/zopefoundation/AccessControl/blob/master/src/AccessControl/requestmethod.py (needed because Zope introspects argument names for other purposes, so a decorator better produce a matching signature). – Martijn Pieters Oct 31 '16 at 15:40
  • (and yikes, I wrote that almost a decade ago.. https://github.com/zopefoundation/Zope/commits/4ef5df9daa4281613dac0e6164c12dd41b924560/lib/python/AccessControl/requestmethod.py). – Martijn Pieters Oct 31 '16 at 15:50
  • So you want to return a function that has the same name as 'f', the same arguments and does the same stuff? This sounds like you don't really want to wrap it. – kabanus Oct 31 '16 at 18:36
  • @kabanus I would like to have a function with the name and arguments list of `f` function, but doing stuff of `g` function (extended `f` function). – pt12lol Oct 31 '16 at 22:15
  • @Martijn Pieters regarding your question about the purpose of this one: I am developing a micro framework for console apps and I have an idea of binding registered (using `argparse`) console options with specific methods. I would like to decorate them with this annotation and generically call them from main block. I tried defining this function in `exec` and return it by `eval` but it failed on referencing to `f` function. could you show how you would see it by using `exec`? – pt12lol Oct 31 '16 at 22:32

0 Answers0