Python:替换模块中类中的函数

34

我正在尝试替换类中定义的函数,以便修改其功能(如内部工作),而不更改实际代码。我以前从未做过这个,因此在替换它时遇到了一些问题。

更改代码将使我访问我的Python库中的包,这不是一个好选择。

例如,如果模块名为testMOD,

class testMOD(object):
    def testFunc(self, variable):
        var = variable
        self.something = var + 12

然后我将导入testMOD,定义一个类(mytest = testMOD()),并访问类中定义的函数testFunc,并将其更改为已经定义好的函数。

例如,

from somemodule import testMOD
mytest = testMOD()

def alternativeFunc(self, variable):
    var = variable
    self.something = var + 1.2

# A problem here
mytest.testFunc = alternativeFunc

正如您所看到的,如果我仅手动覆盖类中的函数并使用自定义函数,它将无法正常工作。

它不会出现任何语法错误,但问题在于替换的函数认为'self'是另一个变量,并且说需要另一个参数来传递'variable'变量(我想这不是一个好的名称)。

我希望将替换函数完全设置为与已替换函数相同,但添加一些代码或进行一些小修改。然而,'self'在类中的行为与预期的不太一致。

是否有办法正确地实现定义的函数以替换导入类的函数?

5个回答

68

我建议4种解决方案,按照我个人意见从最差到最好排序,但当然这也取决于你的具体限制:

  1. 替换实例方法(1):我利用了在Python中函数是描述符的事实,这样我就可以在AlternativeFunc上使用__get__方法将其作为mytest实例的方法,并覆盖mytest实例的testFunc方法(而不覆盖类方法):

class testMOD(object):
    def testFunc(self, variable):
        var = variable
        self.something = var + 12
        print('Original:', self.something)

def alternativeFunc1(self, variable):
    var = variable
    self.something = var + 1.2
    print('Alternative1:', self.something)

mytest1 = testMOD()
mytest1.testFunc(10)   # Original: 22

mytest1.testFunc = alternativeFunc1.__get__(mytest1, testMOD)
mytest1.testFunc(10)   # Alternative1: 11.2
mytestX = testMOD()
mytestX.testFunc(10)   # Original: 22
替换实例方法(2):这次,我使用 types.MethodType ,它比第一个解决方案更易读:

  • import types
    
    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print('Original:', self.something)
    
    def alternativeFunc1(self, variable):
        var = variable
        self.something = var + 1.2
        print('Alternative1:', self.something)
    
    mytest1 = testMOD()
    mytest1.testFunc(10)   # Original: 22
    
    funcType = types.MethodType
    mytest1.testFunc = funcType(alternativeFunc1, mytest1)
    mytest1.testFunc(10)   # Alternative1: 11.2
    mytestX = testMOD()
    mytestX.testFunc(10)   # Original: 22
    
    执行类方法的猴子补丁。与第一种方法不同,它会更改类的任何实例的行为:

  • class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print('Original:', self.something)
    
    def alternativeFunc2(self, variable):
        var = variable
        self.something = var + 1.2
        print('Alternative2:', self.something)
    
    mytest2 = testMOD()
    mytest2.testFunc(10)   # Original: 22
    
    testMOD.testFunc = alternativeFunc2
    mytest2.testFunc(10)   # Alternative2: 11.2
    mytestX = testMOD()
    mytestX.testFunc(10)   # Alternative2: 11.2
    
    创建一个继承自testMOD的类,覆盖该方法:
    class testMODNew(testMOD):
         def testFunc(self, variable):
             var = variable
             self.something = var + 1.2
             print('Alternative3:', self.something)
    
    mytest3 = testMODNew()
    mytest3.testFunc(10) # Alternative3: 11.2
    

    当类是内部类时,即对象仅在某个外部模块内部实例化时,我们应该怎么做?现在我唯一能想到的选择是修改源代码并维护自己的分支。 - deed02392
    1
    方法3只在某些版本的Python中有效吗?我在3.6.8中尝试了它,但它没有像你的实例一样更新现有实例。 - brian_ds

    18

    您可以按照以下方式对此方法进行 技巧上的补丁

    class TestMOD(object):
    
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print(f'original {self.something}')
    
    
    def alternativeFunc(self, variable):
        var = variable
        self.something = var + 1.2
        print(f'alternative {self.something}')
    
    
    if __name__ == '__main__':
    
        test_original = TestMOD()
        test_original.testFunc(12)
    
        TestMOD.testFunc = alternativeFunc
    
        test_alternate = TestMOD()
        test_alternate.testFunc(12)
    

    输出:

    original 24
    alternative 13.2
    

    6
    谢谢回答!当我认为我所做的与你做的完全相同时,我确实感到困惑,后来我意识到我必须更改实际类而不是继承的类变量。 - Cody Chung
    2
    值得一提的是:不需要创建一个新的TestMOD实例。猴子补丁会改变类本身,这也会影响到任何现有的实例。调用test_original.testFunc(12)也会产生同样的效果。 - Edward Falk
    @EdwardFalk,猴子补丁是否也会更改类的描述?例如,更新是否会显示为??TestMod? - mank

    3
    这有点像是一个技巧,但你可以使用Lambda函数:
    mytest.testFunc = lambda *args, **kwargs: alternativeFunc(mytest, *args, **kwargs)
    

    3

    查看Python中的类继承来创建自己的自定义类:

    from somemodule import TestMOD
    
    class YourCustomClass(TestMOD):
    
        # change the function
        def test_func(self, variable):
            #
            #
    
    your_class = YourCustomClass()
    your_class.test_func(x)
    

    1

    由于最初的问题要求调用父类中的函数并做一些额外的事情,因此我认为仅仅替换函数可能存在问题;如果父类被修改(所在模块被更新),则您可能需要相应地修改代码。另外,他们可能不想重建原始函数以添加一些内容到末尾。

    我完全同意创建一个从testMod继承的类是最好的选择,我只是建议从testMod中调用该函数,然后修改结果。

    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            return var + 12
            
    class testMODNew(testMOD):
         def testFunc(self, variable):
             return testMOD.testFunc(self,variable) - 10.8
             
    mytest4 = testMODNew()
    print('Alternative4:', mytest4.testFunc(10)) # Alternative4: 11.2
             
    

    如果您希望具有该类的对象跟踪方法被调用的次数,可以进行其他更改:

    class testMODNew(testMOD):
         __testFuncCount__ = 0
         def testFunc(self, variable):
             self.__testFuncCount__ += 1
             return testMOD.testFunc(self,variable)
             
         def getTestFuncCount(self):
             return self.__testFuncCount__
    
    mytest5 = testMODNew()
    print('Original:',mytest5.testFunc(10)) #Original: 10
    print('Original:',mytest5.testFunc(10)) #Original: 10
    print('testFunc was called', mytest5.getTestFuncCount(), 'times.') 
    #testFunc was called 2 times
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接