views:

710

answers:

5

Suppose I have a generic function f. I want to programmatically create a function f2 that behaves the same as f, but has a customised signature.

More detail

Given a list l and and dictionary d I want to be able to:

  • Set the non-keyword arguments of f2 to the strings in l
  • Set the keyword arguments of f2 to the keys in d and the default values to the values of d

ie. Suppose we have

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code

Then I would want a function with signature:

def f2(x, y, opt=None):
    #My code

A specific use case

This is just a simplified version of my specific use case. I am giving this as an example only.

My actual use case (simplified) is as follows. We have a generic initiation function:

def generic_init(self,*args,**kwargs):
    """Function to initiate a generic object"""
    for name, arg in zip(self.__init_args__,args):
        setattr(self, name, arg)
    for name, default in self.__init_kw_args__.items():
        if name in kwargs:
            setattr(self, name, kwargs[name])
        else:
            setattr(self, name, default)

We want to use this function in a number of classes. In particular, we want to create a function init that behaves like generic_init, but has the signature defined by some class variables at creation time:

class my_class:
    __init_args__=["x", "y"]
    __kw_init_args__={"my_opt": None}

__init__=create_initiation_function(my_class, generic_init)
setattr(myclass, "__init__", __init__)

We want create_initiation_function to create a new function with the signature defined using init_args and kw_init_args. Is it possible to writ create_initiation_function?

Please note:

  • If I just wanted to improve the help, I could set doc.
  • We want to set the function signature on creation. After that, it doesn't need to be changed.
  • Instead of creating a function like generic_init, but with a different signature we could create a new function with the desired signature that just calls generic_init
  • We want to define create_initiation_function. We don't want to manually specify the new function!

Related

A: 

Edit 1: Answering new question:

You ask how you can create a function with this signature:

def fun(a, b, opt=None):
    pass

The correct way to do that in Python is thus:

def fun(a, b, opt=None):
    pass

Edit 2: Answering explanation:

"Suppose I have a generic function f. I want to programmatically create a function f2 that behaves the same as f, but has a customised signature."

def f(*args, **kw):
    pass

OK, then f2 looks like so:

def f2(a, b, opt=None):
    f(a, b, opt=opt)

Again, the answer to your question is so trivial, that you obviously want to know something different that what you are asking. You really do need to stop asking abstract questions, and explain your concrete problem.

Lennart Regebro
Doesn't answer the question. Sorry for being unclear, I have clarified it now.
Casebash
It did answer the question. I've also updated the answer to reflect your changes. It again answers the question. It probably doens't tell you what you want to know, but you have to tell us what you want to know first.
Lennart Regebro
I was pretty sure that the meaning was clear after I edited it, but I have edited it again.
Casebash
No, it's still not clear. What do you mean by "create a function"? What is that function supposed to do?
Miles
Okay, sorry for everyone who I have confused. I really hope I've managed to make myself clear this time.
Casebash
Programmatically is the key word
Casebash
Yeah, but the code is the same no matter of you create that code by typing it in or with a program.
Lennart Regebro
+5  A: 

For your usecase, having a docstring in the class/function should work -- that will show up in help() okay, and can be set programmatically (func.__doc__ = "stuff").

I can't see any way of setting the actual signature. I would have thought the functools module would have done it if it was doable, but it doesn't, at least in py2.5 and py2.6.

You can also raise a TypeError exception if you get bad input.

Hmm, if you don't mind being truly vile, you can use compile()/eval() to do it. If your desired signature is specified by arglist=["foo","bar","baz"], and your actual function is f(*args, **kwargs), you can manage:

argstr = ", ".join(arglist)
fakefunc = "def func(%s):\n    return real_func(%s)\n" % (argstr, argstr)
fakefunc_code = compile(fakefunc, "fakesource", "exec")
fakeglobals = {}
eval(fakefunc_code, {"real_func": f}, fakeglobals)
f_with_good_sig = fakeglobals["func"]

help(f)               # f(*args, **kwargs)
help(f_with_good_sig) # func(foo, bar, baz)

Changing the docstring and func_name should get you a complete solution. But, uh, eww...

Anthony Towns
I should have thought of eval. Anything is possible with it.
Casebash
This is the pattern used by the decorator package -- http://pypi.python.org/pypi/decorator/
chrispy
A: 

You can't do this with live code.

That is, you seem to be wanting to take an actual, live function that looks like this:

def f(*args, **kwargs):
    print args[0]

and change it to one like this:

def f(a):
    print a

The reason this can't be done--at least without modifying actual Python bytecode--is because these compile differently.

The former results in a function that receives two parameters: a list and a dict, and the code you're writing operates on that list and dict. The second results in a function that receives one parameter, and which is accessed as a local variable directly. If you changed the function "signature", so to speak, it'd result in a function like this:

def f(a):
    print a[0]

which obviously wouldn't work.

If you want more detail (though it doesn't really help you), a function that takes an *args or *kwargs has one or two bits set in f.func_code.co_flags; you can examine this yourself. The function that takes a regular parameter has f.func_code.co_argcount set to 1; the *args version is 0. This is what Python uses to figure out how to set up the function's stack frame when it's called, to check parameters, etc.

If you want to play around with modifying the function directly--if only to convince yourself that it won't work--see this answer for how to create a code object and live function from an existing one to modify bits of it. (This stuff is documented somewhere, but I can't find it; it's nowhere in the types module docs...)

That said, you can dynamically change the docstring of a function. Just assign to func.__doc__. Be sure to only do this at load time (from the global context or--most likely--a decorator); if you do it later on, tools that load the module to examine docstrings will never see it.

Glenn Maynard
I don't need to dynamically change the signature of a function, just to be able create a function which does the same as another function and has a different signature
Casebash
That's the ... same thing.
Glenn Maynard
Not quite. As pointed out by Towns, we could just create a new function using eval that can call the generic function
Casebash
A: 

"We want create_initiation_function to change the signature"

Please don't do this.

"We want to use this function in a number of classes"

Please use ordinary inheritance.

There's no value in having the signature "changed" at run time.

You're creating a maintenance nightmare. No one else will ever bother to figure out what you're doing. They'll simply rip it out and replace it with inheritance.

Do this instead. It's simple and obvious and makes your generic init available in all subclasses in an obvious, simple, Pythonic way.

class Super( object ):
    def __init__( self, **args, **kwargs ):
        # the generic __init__ that we want every subclass to use

class SomeSubClass( Super ):
    def __init__( self, this, that, **kwdefaults ):
        super( SomeSubClass, self ).__init__( this, that, **kwdefaults )

class AnotherSubClass( Super ):
    def __init__( self, x, y, **kwdefaults ):
        super( AnotherSubClass, self ).__init__( x, y, **kwdefaults )
S.Lott
A: 

Maybe I didn't understand the problem well, but if it's about keeping the same behavior while changing the function signature, then you can do something like :

# define a function
def my_func(name, age) :
    print "I am %s and I am %s" % (name, age)

# label the function with a backup name
save_func = my_func

# rewrite the function with a different signature
def my_func(age, name) :
    # use the backup name to use the old function and keep the old behavior
    save_func(name, age)

# you can use the new signature
my_func(35, "Bob")

This outputs :

I am Bob and I am 35
e-satis
Needs to be programmatically. Its hard to explain, but I suppose one last edit wouldn't kill me
Casebash