Python 动态添加函数

22 投票
7 回答
19652 浏览
提问于 2025-04-15 22:27

我该怎么在一个已经存在的函数里添加代码呢?可以是在函数之前或者之后。

比如说,我有一个类:

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

有没有办法做到这一点呢?

7 个回答

6

这个回答让你可以在不创建额外包装的情况下修改原来的函数。我发现了以下两种原生的 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 能够获取正确的新源代码。

10

通常,给一个函数增加功能的方法是使用装饰器(配合wraps函数)。

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"

这样做不需要复杂的编程,但你的例子可能是大大简化过的,跟你实际需要的情况不太一样。也许如果你能提供更多关于你具体需求的细节,我们可以更好地建议你用Python的方式来解决。

编辑:看起来你想要调用函数,你可以用一个函数列表来代替消息列表。例如:

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"

注意,如果你想添加的函数是所有类的实例都要调用的,那么你应该把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"
27

如果你想的话,可以用一个装饰器来修改这个函数。不过,因为这个装饰器不是在函数最开始定义的时候就应用的,所以你不能用 @ 这种简写方式来使用它。

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

请注意,这样做会影响到已经存在的实例的方法。

编辑:把装饰器的参数列表改成了更好的版本,使用了 argskwargs

撰写回答