【问题标题】:Python add to a function dynamicallyPython动态添加到函数
【发布时间】:2011-02-16 21:10:54
【问题描述】:

如何在现有函数之前或之后添加代码?

例如,我有一个班级:

 class A(object):
     def test(self):
         print "here"

我如何编辑具有元编程的类以便我这样做

 class A(object):
     def test(self):
         print "here"

         print "and here"

也许可以通过某种方式附加另一个函数进行测试?

添加另一个功能,例如

 def test2(self):
      print "and here"

把原来的改成

 class A(object):
     def test(self):
         print "here"
         self.test2()

有没有办法做到这一点?

【问题讨论】:

    标签: python metaprogramming


    【解决方案1】:

    此答案允许您在不创建包装器的情况下修改原始函数。我找到了以下两种原生 python 类型,它们对回答您的问题很有用:

    types.FunctionType
    

    types.CodeType
    

    这段代码似乎可以完成这项工作:

    import inspect
    import copy
    import types
    import dill
    import dill.source
    
    
    #Define a function we want to modify:
    def test():
        print "Here"
    
    #Run the function to check output
    print '\n\nRunning Function...'
    test()
    #>>> Here
    
    #Get the source code for the test function:
    testSource = dill.source.getsource(test)
    print '\n\ntestSource:'
    print testSource
    
    
    #Take the inner part of the source code and modify it how we want:
    newtestinnersource = ''
    testSourceLines = testSource.split('\n')
    linenumber = 0 
    for line in testSourceLines:
        if (linenumber > 0):
            if (len(line[4:]) > 0):
                newtestinnersource += line[4:] + '\n'
        linenumber += 1
    newtestinnersource += 'print "Here2"'
    print '\n\nnewtestinnersource'
    print newtestinnersource
    
    
    #Re-assign the function's code to be a compiled version of the `innersource`
    code_obj = compile(newtestinnersource, '<string>', 'exec')
    test.__code__ = copy.deepcopy(code_obj)
    print '\n\nRunning Modified Function...'
    test() #<- NOW HAS MODIFIED SOURCE CODE, AND PERFORMS NEW TASK
    #>>>Here
    #>>>Here2
    

    待办事项: 更改此答案,以便dill.source.getsource 获得正确的新源代码。

    【讨论】:

    • 然而,我发现自己很生气,因为我的解决方案不适用于函数修改循环的多次迭代。 dill.source.getsource 只对原版函数有效,修改函数后无法再次使用获取源代码。看起来,python 只跟踪一次性最初编译的源代码,而不跟踪源代码对函数的更改。原则上,您可以获取函数的编译版本,然后重构 python 代码……但这似乎很愚蠢。
    • 这正是我想要的!取一个函数,将其转换为源代码,修改源代码并重新编译!对于大规模模拟中的三重嵌套循环内的元编程非常有用。您可以根据条件预先创建复杂的函数,您不必再在循环内检查:)(动态修改任何类型的成本函数等)
    • 我认为终于有人发现这很有用真是太好了。现在我们应该修改我的代码片段以更改“源”,以便 dill.source.getsource 正确获取修改后的源代码。
    【解决方案2】:

    复制粘贴,尽情享受吧!!!!

    #!/usr/bin/env python 
    
    def say(host, msg): 
       print '%s says %s' % (host.name, msg) 
    
    def funcToMethod(func, clas, method_name=None): 
       setattr(clas, method_name or func.__name__, func) 
    
    class transplant: 
       def __init__(self, method, host, method_name=None): 
          self.host = host 
          self.method = method 
          setattr(host, method_name or method.__name__, self) 
    
       def __call__(self, *args, **kwargs): 
          nargs = [self.host] 
          nargs.extend(args) 
          return apply(self.method, nargs, kwargs) 
    
    class Patient: 
       def __init__(self, name): 
          self.name = name 
    
    if __name__ == '__main__': 
       jimmy = Patient('Jimmy') 
       transplant(say, jimmy, 'say1') 
       funcToMethod(say, jimmy, 'say2') 
    
       jimmy.say1('Hello') 
       jimmy.say2(jimmy, 'Good Bye!') 
    

    【讨论】:

      【解决方案3】:

      上面有很多非常好的建议,但我没有看到的是通过调用传入一个函数。可能看起来像这样:

      class A(object):
          def test(self, deep=lambda self: self):
              print "here"
              deep(self)
      def test2(self):
          print "and here"
      

      使用这个:

      >>> a = A()
      >>> a.test()
      here
      >>> a.test(test2)
      here
      and here
      

      【讨论】:

        【解决方案4】:

        如果你的 A 类继承自对象,你可以这样做:

        def test2():
            print "test"
        
        class A(object):
            def test(self):
                setattr(self, "test2", test2)
                print self.test2
                self.test2()
        
        def main():
            a = A()
            a.test()
        
        if __name__ == '__main__':
            main()
        

        此代码没有兴趣,您不能在您添加的新方法中使用 self。 我只是觉得这段代码很有趣,但我永远不会使用它。我不喜欢动态改变对象本身。

        这是最快的方式,也更容易理解。

        【讨论】:

          【解决方案5】:

          您可以根据需要使用装饰器来修改功能。但是,由于它不是在函数初始定义时应用的装饰器,因此您将无法使用 @ 语法糖来应用它。

          >>> class A(object):
          ...     def test(self):
          ...         print "orig"
          ...
          >>> first_a = A()
          >>> first_a.test()
          orig
          >>> def decorated_test(fn):
          ...     def new_test(*args, **kwargs):
          ...         fn(*args, **kwargs)
          ...         print "new"
          ...     return new_test
          ...
          >>> A.test = decorated_test(A.test)
          >>> new_a = A()
          >>> new_a.test()
          orig
          new
          >>> first_a.test()
          orig
          new
          

          请注意,它也会修改现有实例的方法。

          EDIT:使用argskwargs将装饰器的参数列表修改为更好版本

          【讨论】:

          • 来自第三方的包呢,比如one
          【解决方案6】:

          向函数添加功能的典型方法是使用decorator(使用the wraps function):

          from functools import wraps
          
          def add_message(func):
              @wraps
              def with_additional_message(*args, **kwargs)
                  try:
                      return func(*args, **kwargs)
                  finally:
                      print "and here"
              return with_additional_message
          
          class A:
              @add_message
              def test(self):
                  print "here"
          

          当然,这实际上取决于您要完成的工作。我经常使用装饰器,但如果我只想打印额外的消息,我可能会做类似的事情

          class A:
              def __init__(self):
                  self.messages = ["here"]
          
              def test(self):
                  for message in self.messages:
                      print message
          
          a = A()
          a.test()    # prints "here"
          
          a.messages.append("and here")
          a.test()    # prints "here" then "and here"
          

          这不需要元编程,但是您的示例可能与您实际需要做的相比大大简化了。也许如果您发布有关您的特定需求的更多详细信息,我们可以更好地建议 Pythonic 方法是什么。

          编辑:由于您似乎想要调用函数,因此您可以拥有函数列表而不是消息列表。例如:

          class A:
              def __init__(self):
                  self.funcs = []
          
              def test(self):
                  print "here"
                  for func in self.funcs:
                      func()
          
          def test2():
              print "and here"
          
          a = A()
          a.funcs.append(test2)
          a.test()    # prints "here" then "and here"
          

          请注意,如果您想添加将被A 的所有实例调用的函数,那么您应该将funcs 设为类字段而不是实例字段,例如

          class A:
              funcs = []
              def test(self):
                  print "here"
                  for func in self.funcs:
                      func()
          
          def test2():
              print "and here"
          
          A.funcs.append(test2)
          
          a = A()
          a.test()    # print "here" then "and here"
          

          【讨论】:

            【解决方案7】:

            为什么不使用继承?

            class B(A):
                def test(self):
                    super(B, self).test()
                    print "and here"
            

            【讨论】:

            • 简而言之,我必须动态地将父母添加到一个班级,所以我不能轻易地调用超级,而不首先做问题的要求
            • 很公平。装饰器看起来肯定是要走的路。
            猜你喜欢
            • 2016-06-15
            • 2023-03-22
            • 2019-05-16
            • 1970-01-01
            • 2021-10-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-05
            相关资源
            最近更新 更多