如何在猴子修补时调用原始方法?

时间:2012-01-04 11:39:25

标签: python oop python-3.x monkeypatching function-overriding

我在主项目中有一堂课,我不想改变。

class A():
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def name(self):
        # this method could be much more complex
        return self.lastname.upper()

我正在尝试构建一个插件mechansim。到目前为止,我有一个像这样的扩展点:

if __name__ == '__main__':
    ''' The main project has an extension point that allows me to do'''
    # for each class extension such as AExtended:
    A.name = AExtended.name
    ''' After the extensions are loaded, some behaviours may be changed'''
    a = A("John", "Doe")
    print(a.name())

插件可以这样写:

class AExtended(A):
    ''' This is an extension I provide through a plugin mechanism
    '''
    def name(self):
        return self.firstname + ' ' + self.lastname.upper()

这一切都很有效。我现在得到“John DOE”。

我的问题是原始name()方法可能非常复杂。换句话说,我无法在self.lastname.upper()中致电AExtended。我想调用“超级”方法,它不再存在,因为它已被覆盖。

如何更改代码,以实现以下目标:

class AExtended(A):
    def name(self):
        # I'm looking for a way to call the base implementation that was in A.name()
        return self.firstname + ' ' + parent.name()

感谢您的帮助!

编辑:我尝试做的一些解释。

  • 我想让插件修补A的行为。我不能改变A
  • 的现有消费者
  • 有许多类A可以更改,我希望插件能够完全控制并负责
  • 这是真的AExtended不必从A继承,但这是一种访问self.firstname的简单方法。如果可以提供帮助,我可以使用不同的设计模式。

我有一个解决方法,但它不是很优雅,很难概括

class AExtended(A):
    def name(self):
        # I'm looking for a way to call the base implementation that was in A.name()
        return self.firstname + ' ' + self.parentname()
#in main
A.parentname = A.name
A.name = AExtended.name

5 个答案:

答案 0 :(得分:15)

这就是我们所说的'装饰'模式。替换名称的原始重新分配,以使其调用函数,而不是原始函数。然后它返回一个新函数。

def name_decorator(method):
    def decorate_name(self=None):
        return stuff + method(self)
    return decorate_name
A.name = name_decorator(A.name)

稍后,调用A.name会将decorate_name作为当前实例调用self,并且method将可用于指向重新分配时该功能的{{1}}

答案 1 :(得分:6)

以下是我所暗示的完整示例。随意对我大喊大叫,让我合并我的答案,或者将其中的一个或者其他内容投降,更容易提供替代作为新的答案。我会让代码进行讨论而不是解释它。 :)

## Some shared class that is used all over the place and needs to be patched.

class A(object):
    def __init__(self):
        self.firstname = 'Bob'

    # Print my first name.
    def name(self):
        return self.firstname

    # Use this to allow patching arbitrary methods...
    @classmethod
    def patch(cls, func_name):
        def patch_by_name(new_func):
            old_func = getattr(cls, func_name)
            def patched_func(self):
                return new_func(self, old_func)
            setattr(cls, func_name, patched_func)
        return patch_by_name

## Some other area of the code where you want to throw in a patch

class PatchedA(A):  # doesn't need to subclass, but comes in handy sometimes
    @A.patch('name')
    def name(self, orig_func):
        return 'I am ' + orig_func(self) + 'McWizwaz'

print 'Who are you, A class?'
print A().name()  # prints 'I am Bob McWizwaz'

答案 2 :(得分:2)

class ABase(object):
    def name(self):
        pass

class A(object):
    pass

class AExtension(ABase):
    def name(self):
        return ABase.name(self)

A.name = AExtension.name

答案 3 :(得分:1)

在Python等语言中可能并不总是最好的一个选项是使用this non-standard package中的@override装饰器。但是,只有当您的两个函数处理不同类型或不同数量的参数时,这才是可行的选项。除此之外,除了重命名你的功能之外,你无能为力。

答案 4 :(得分:1)

站在巨人(@spencer)的肩膀上,这是一个更通用的示例。它使一个补丁可以对Original类进行修补,而无需触及其源代码。在此版本中,参数随处可见,并为新方法提供了对任意上下文的引用。

class Original:
    def f( self, a, b = None ):
        print( "f", a, b )
        self.g( b )

    def g( self, b ):
        print( "g", b )

class Patcher:
    def prepare_class( self, clazz ):
        @classmethod
        def on_class_patcher( cls, func_name, context ):
            def patch_by_name( new_func) :
                old_func = getattr( cls, func_name )
                def patched_func( self, *args, **kwargs ):
                    return new_func( self, old_func, context, *args, **kwargs )
                setattr( cls, func_name, patched_func )
            return patch_by_name

        setattr( clazz, "patch", on_class_patcher )

class Another:
    def log( self, level, info ):
        print( level, info )

现在,让我们修补一些东西:

obj = Original()
obj.f( 1, b = "hello" )

p = Patcher()
p.prepare_class( clazz = Original )

logger = Another()

@Original.patch( "f", context = logger  )
def new_f( self, old_f, context, a, b ):
    print( "new_f", a, b )
    context.log( "zzz", a )
    old_f( self, a, b )

obj = Original()
obj.f( 1, b = "hello" )

输出为:

f 1 hello
g hello
new_f 1 hello
zzz 1
f 1 hello
g hello