如何扩展 Class 实例

How to extend Class instance

MyClassmodule.py 中定义。我们没有办法修改它。但我们确实知道 Class 定义如下所示:

class MyClass:
    def method(self, msg):
        print 'from method:', msg

我从导入模块开始我的脚本,然后声明一个对象的实例:

import module    
foo = module.MyClass()

然后我自己写函数:

def function(msg):
    print 'from function:', msg

现在,每次使用 foo.method('') 时,我都想调用 function(),因此它也会打印相同的消息。

这种情况可以称为monkey patching吗?如何实现所需要的?

在发布更好的解决方案之前完成此解决方案...

class MyClass:
    def method(self, msg):
        print 'from method:', msg

def function(msg, callback):
    print 'from function:', msg
    callback(msg)

foo = MyClass()
foo.function = function
foo.function(msg='message', callback=foo.method)

是的,它叫做 monkey-patching。

这基本上是装饰,但是在 class 已经定义之后手动完成。

from functools import wraps

def wrapper(f):
    @wraps(f)
    def wrapped(*args, **kwargs):
        myFunction()
        return f(*args, **kwargs)
    return wrapped

MyClass.printThis = wrapper(MyClass.printThis)

它将影响 MyClass 的所有实例,甚至是那些在应用补丁之前创建的实例。

如果不需要动态修改运行时行为,避免monkey-patching并且更喜欢使用继承来自定义行为,如评论中所建议的那样。它不那么骇人听闻。

你也可以子class它:

class MyClass:
    def method(self, msg):
        print 'from method:', msg

def function(msg):
    print 'from function:', msg

class MyNewClass(MyClass):
    def method(self, msg):
        function(msg)
        MyClass.method(self, msg)

并像这样使用它:

>>> a = MyNewClass()
>>> a.method("test")
from function: test
from method: test

或者,如果你想让你的 class 成为 "new-style" class(对于 Python 2 - 根据你的打印语句判断) - 只需要 MyClass 继承自 object 然后你可以使用 super:

class MyClass(object):  # object added here
    def method(self, msg):
        print 'from method:', msg

def function(msg):
    print 'from function:', msg

class MyNewClass(MyClass):
    def method(self, msg):
        function(msg)
        super(self.__class__, self).method(msg)  # super added here

这是 的替代方案,它也涉及 monkey-patching。但是,它是通过 unittest.mock 提供的功能来实现的。这种方法的优点是使用上下文管理器在有限范围内自动应用和删除补丁:

from unittest import mock

# This class would be defined in some third-party library
class MyClass:
    def method(self, msg):
        print('from method:', msg)


def function(msg):
    print('from function:', msg)


old_method = MyClass.method


def new_method(self, msg):
    old_method(self, msg)
    function(msg)


# The patch is only applied within this scope
with mock.patch.object(MyClass, 'method', new_method):
    foo = MyClass()
    foo.method('message with patched')

# By this point MyClass is "back to normal"
print('---')
foo.method('message with original')

输出

from method: message with patched
from function: message with patched
---
from method: message with original