【问题标题】:Dynamic/runtime method creation (code generation) in PythonPython 中的动态/运行时方法创建(代码生成)
【发布时间】:2010-10-06 17:04:16
【问题描述】:

我需要在运行时为方法生成代码。能够运行任意代码并拥有文档字符串非常重要。

我想出了一个结合execsetattr 的解决方案,这是一个虚拟示例:

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__

是否有更好/更安全/更惯用的方法来实现相同的结果?

【问题讨论】:

  • 您为什么需要它,您是否考虑过 Python 中的其他元编程工具?
  • 我愿意接受建议 :-) 我需要这个来为 PLY 生成规则,它需要它们作为带有文档字符串的方法。为了自动化一些样板代码,我可以在运行时循环生成一些规则
  • 你能举一个更好的例子,或者解释更多吗?您给出的示例不是很动态,因为它是一个硬编码字符串,我无法理解为什么您不能使用调度程序、多态性、元类等
  • 我想生成几个这样的方法,它们的名称和文档字符串以某种“循环”的方式变化,即 10 个名为 dynamo1..10 的方法,文档字符串中也包含 dynamo1..10 "

标签: python metaprogramming exec


【解决方案1】:

更通用的解决方案:

您可以调用 Dummy 类实例的任何方法。 文档字符串是根据方法名称生成的。 任何输入参数的处理都是通过返回它们来演示的。

代码

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

输出

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

【讨论】:

    【解决方案2】:
    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()
    

    【讨论】:

      【解决方案3】:

      请原谅我的英语不好。

      我最近需要生成动态函数来绑定每个菜单项以打开 wxPython 上的特定框架。这就是我所做的。

      首先,我创建一个菜单项和框架之间的映射列表。

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

      映射上的第一项是菜单项,最后一项是要打开的框架。接下来,我将每个菜单项中的 wx.EVT_MENU 事件绑定到特定框架。

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

      genfunc函数是动态函数生成器,代码如下:

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

      【讨论】:

        【解决方案4】:

        Python 会让你在函数中声明一个函数,所以你不必做exec 的诡计。

        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)
        

        如果您想拥有该函数的多个版本,可以将所有这些放在一个循环中,并在 setattr 函数中更改您对它们的名称:

        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
        

        (我知道这不是很好的代码,但它明白了这一点)。至于设置文档字符串,我知道这是可能的,但我必须在文档中查找它。

        编辑:您可以通过dynamo.__doc__ 设置文档字符串,因此您可以在循环体中执行以下操作:

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

        另一个编辑:在@eliben 和@bobince 的帮助下,应该解决了闭包问题。

        【讨论】:

        • 'i' 将在循环完成后的每个 dynamo 实例中为 10。每次循环时,变量都不会反弹。这是在 Python(和其他类似语言)中使用闭包的一大难题。
        • 啊,废话。感谢您的澄清。有没有可行的技术?
        • 贾斯汀,有关此问题的解决方案,请参阅:stackoverflow.com/questions/233673/lexical-closures-in-python/…
        【解决方案5】:

        基于 Theran 的代码,但将其扩展到类上的方法:

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

        应该打印的内容:

        in dynamo 0 in dynamo 1

        【讨论】:

        • 谢谢,这很好用。实际上,在这种情况下,“exec”可以不用——但这只是因为方法的代码是相对恒定的,并且不真的(打印的字符串不计算在内)依赖于方法本身
        • 解决方案对这个问题有好处,但将所有内容放在单个类中会更有帮助 class Dynamo(object):
        • 如果我想给每个函数添加一个装饰器怎么办。我怎样才能做到这一点 ?我尝试了类似在 innerdynamo 函数上方使用 @my_decorator 的方法。但是,它没有用。有什么建议吗?
        • @user699540:使用你描述的方式使用装饰器可以正常工作,所以肯定有其他问题 - 但如果没有更多信息,就不可能说出它是什么。
        【解决方案6】:

        函数文档字符串和名称是可变属性。你可以在内部函数中做任何你想做的事情,甚至可以有多个版本的内部函数供 makedynamo() 选择。无需使用字符串构建任何代码。

        这是解释器的一个sn-p:

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

        【讨论】:

          猜你喜欢
          • 2011-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-22
          • 2010-11-23
          • 2010-12-17
          • 2015-04-04
          相关资源
          最近更新 更多