56

I need to generate code for a method at runtime. It's important to be able to run arbitrary code and have a docstring.

I came up with a solution combining exec and setattr, here's a dummy example:

class Viking(object):
    def __init__(self):
        code = '''
            def dynamo(self, arg):
                """ dynamo's a dynamic method!
                """
                self.weight += 1
                return arg * self.weight
            '''
        self.weight = 50

        d = {}
        exec code.strip() in d
        setattr(self.__class__, 'dynamo', d['dynamo'])


if __name__ == "__main__":
    v = Viking()
    print v.dynamo(10)
    print v.dynamo(10)
    print v.dynamo.__doc__

Is there a better / safer / more idiomatic way of achieving the same result?

4
  • Why do you need that, did you consider the other metaprogramming facilities in Python? Commented Feb 10, 2009 at 17:44
  • I'm open to suggestions :-) I need this to generate rules for PLY, which needs them as methods with docstrings. To automate some boilerplate code, I can generate some rules in a loop at runtime Commented Feb 10, 2009 at 18:00
  • Can you give a better example, or explain more? The example you give isn't very dynamic since its a hard coded string, I'm having trouble understanding why you can't use dispatchers, polymorphism, metaclasses, etc Commented Feb 10, 2009 at 18:17
  • I'll want to generate several such methods, varying in their names and docstrings in some "loopy" way, i.e. 10 methods named dynamo1..10 with the docstring also having dynamo1..10 in it" Commented Feb 10, 2009 at 18:23

6 Answers 6

87

Based on Theran's code, but extending it to methods on classes:

class Dynamo(object):
    pass

def add_dynamo(cls, i):
    def innerdynamo(self):
        print "in dynamo %d" % i
    innerdynamo.__doc__ = "docstring for dynamo%d" % i
    innerdynamo.__name__ = "dynamo%d" % i
    setattr(cls, innerdynamo.__name__, innerdynamo)

for i in range(2):
    add_dynamo(Dynamo, i)

d = Dynamo()
d.dynamo0()
d.dynamo1()

Which should print:

in dynamo 0
in dynamo 1
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, this works nicely. Indeed, in this case the 'exec' can be spared - but only because the code of the method is relatively constant and doesn't really (printed strings don't count) depend on the method itself
the solution is good for the question, but it would be more helpful to put everything in a single class class Dynamo(object):
What if I want to add a decorator to each function. How can I do that ? I tried something like using @my_decorator right above the innerdynamo function. But, it did not work. Any suggestions ?
@user699540: This works fine using a decorator the way you describe, so there must be some other issue—but it's impossible to say what it is without more information.
14

Function docstrings and names are mutable properties. You can do anything you want in the inner function, or even have multiple versions of the inner function that makedynamo() chooses between. No need to build any code out of strings.

Here's a snippet out of the interpreter:

>>> def makedynamo(i):
...     def innerdynamo():
...         print "in dynamo %d" % i
...     innerdynamo.__doc__ = "docstring for dynamo%d" % i
...     innerdynamo.__name__ = "dynamo%d" % i
...     return innerdynamo

>>> dynamo10 = makedynamo(10)
>>> help(dynamo10)
Help on function dynamo10 in module __main__:

dynamo10()
    docstring for dynamo10

Comments

12

Python will let you declare a function in a function, so you don't have to do the exec trickery.

def __init__(self):

    def dynamo(self, arg):
        """ dynamo's a dynamic method!
        """
        self.weight += 1
        return arg * self.weight
    self.weight = 50

    setattr(self.__class__, 'dynamo', dynamo)

If you want to have several versions of the function, you can put all of this in a loop and vary what you name them in the setattr function:

def __init__(self):
    for i in range(0, 10):

        def dynamo(self, arg, i=i):
            """ dynamo's a dynamic method!
            """
            self.weight += i
            return arg * self.weight

        setattr(self.__class__, 'dynamo_' + i, dynamo)
        self.weight = 50

i=i is for solving a problem with closures, see How do lexical closures work?.

(I know this isn't great code, but it gets the point across). As far as setting the docstring, I know that's possible but I'd have to look it up in the documentation.

You can set the docstring via dynamo.__doc__, so you could do something like this in your loop body:

dynamo.__doc__ = "Adds %s to the weight" % i

3 Comments

‘i’ will be 10 in each instance of dynamo once the loop has finished. The variable is not rebound each time around the loop. This is one of the big gotchas about using closures in Python (and other similar languages).
Ah, drat. Thanks for the clarification. Is there a technique that will work?
Justin, for the solution of this gotcha see: stackoverflow.com/questions/233673/lexical-closures-in-python/…
1
class Dynamo(object):
    def __init__(self):
        pass

    @staticmethod
    def init(initData=None):
        if initData is not None:
            dynamo= Dynamo()
            for name, value in initData.items():
                code = '''
def %s(self, *args, **kwargs):
%s
                            ''' % (name, value)
                result = {}
                exec code.strip() in result
                setattr(dynamo.__class__, name, result[name])

            return dynamo

        return None

service = Dynamo.init({'fnc1':'pass'})
service.fnc1()

Comments

0

A bit more general solution:

You can call any method of an instance of class Dummy. The docstring is generated based on the methods name. The handling of any input arguments is demonstrated, by just returning them.

Code

class Dummy(object):

    def _mirror(self, method, *args, **kwargs):
        """doc _mirror"""
        return args, kwargs

    def __getattr__(self, method):
        "doc __getattr__"

        def tmp(*args, **kwargs):
            """doc tmp"""
            return self._mirror(method, *args, **kwargs)
        tmp.__doc__ = (
                'generated docstring, access by {:}.__doc__'
                .format(method))
        return tmp

d = Dummy()    
print(d.test2('asd', level=0), d.test.__doc__)
print(d.whatever_method(7, 99, par=None), d.whatever_method.__doc__)

Output

(('asd',), {'level': 0}) generated docstring, access by test.__doc__
((7, 99), {'par': None}) generated docstring, access by whatever_method.__doc__

Comments

-1

I recently need to generate dynamic function to bind each menu item to open particular frame on wxPython. Here is what I do.

First, I create a list of mapping between the menu item and the frame.

menus = [(self.menuItemFile, FileFrame), (self.menuItemEdit, EditFrame)]

The first item on the mapping is the menu item and the last item is the frame to be opened. Next, I bind the wx.EVT_MENU event from each of the menu item to particular frame.

for menu in menus:
    f = genfunc(self, menu[1])
    self.Bind(wx.EVT_MENU, f, menu[0])

The genfunc function is the dynamic function builder, here is the code:

def genfunc(parent, form):
    def OnClick(event):
        f = form(parent)
        f.Maximize()
        f.Show()
    return OnClick

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.