Dynamic/runtime method creation (code generation) in Python

后端 未结 6 1377
死守一世寂寞
死守一世寂寞 2020-11-28 03:45

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 exe

相关标签:
6条回答
  • 2020-11-28 04:01

    Pardon me for my bad English.

    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])
    

    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
    
    0 讨论(0)
  • 2020-11-28 04:03

    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
    
    0 讨论(0)
  • 2020-11-28 04:09

    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 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.

    Edit: 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
    

    Another Edit: With help from @eliben and @bobince, the closure problem should be solved.

    0 讨论(0)
  • 2020-11-28 04:10
    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()
    
    0 讨论(0)
  • 2020-11-28 04:14

    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
    
    
    0 讨论(0)
  • 2020-11-28 04:19

    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__
    
    0 讨论(0)
提交回复
热议问题