修补 Python 中 class 的所有实例
Patching all instances of a class in Python
我在 Python 3.5.1。
我写了一个库,在某些情况下(当文件直接是 运行,即 __name__ == '__main__'
时)我想在 class 之一中装饰某些方法。它应该装饰所有可以创建的实例。我想以一种非侵入性的方式进行,即理想情况下,我库中的 class 不需要任何特殊代码。
经过一段时间,我成功实现了这样的东西,符合我的要求:
def patch(clazz, name, replacement):
def wrap_original(orig):
# when called with the original function, a new function will be returned
# this new function, the wrapper, replaces the original function in the class
# and when called it will call the provided replacement function with the
# original function as first argument and the remaining arguments filled in by Python
def wrapper(*args, **kwargs):
return replacement(orig, *args, **kwargs)
return wrapper
orig = getattr(clazz, name)
setattr(clazz, name, wrap_original(orig))
def replacement_function(orig, self, ... other argumnents ...):
# orig here is the original function, so can be called like this:
# orig(self, ... args ...)
pass
patch(mylib.MyClass, 'method_name', replacemment_function)
令人惊讶的是,这段代码有效,虽然我还没有用 class 方法测试它,但我现在不需要它。它还会修补在修补之前创建的实例,尽管我还不确定它是否好用;d
上面的代码可以说是很难的,写完之后我需要花点时间思考一下它的工作原理,才能写出解释注释。我想要更简单的东西。
问题:Python 库中是否有任何东西可以使这样的代码变得不必要,它已经实现了我正在做的事情,但更好?
您的方法似乎是最符合 Pythonic 的方法。
Gevent,一个使用猴子补丁的流行库,performs monkey patching 几乎与您描述的方式相同。
方法是在查找实例时动态创建的;实例没有所有方法的副本,相反 descriptor protocol 从 class 获取函数并根据需要将这些函数绑定到实例。这就是为什么 monkeypatching class 在这里起作用的原因; instance.method_name
将在执行属性查找时找到 mylib.MyClass.method_name
。
默认库中没有执行您在此处执行的操作的任何内容,不,因为不同的代码可能需要不同的处理委托回旧方法的模式。
您的方法看起来非常接近 how the Mercurial project 支持函数包装,因为原始方法被传递到包装器中。
另一种方法是创建一个 "null" 装饰器函数,然后使用您的条件逻辑在该函数和 "real" 装饰器之间切换:
from decorator_lib import real_decorator
def fake_decorator(fun):
return fun
if __name__ == '__main__':
my_decorator = real_decorator
else:
my_decorator = fake_decorator
# ... elsewhere in the module ...
@my_decorator
def method(self, a, b, c):
pass
# ... finally:
if __name__ == '__main__':
run_self_tests_or_whatever()
这里的一位 post 作者遗憾地删除了 her/his post,他指引我进入 functools
模块。最后,我做出了以下决定:
def replacement(self, orig, ... other arguments ...):
# orig here is the original function, so can be called like this:
# orig(self, ... args ...)
pass
mylib.MyClass.my_method = functools.partialmethod(replacement, mylib.MyClass.my_method)
orig
和 self
参数需要交换位置,因为 partialmethod
将第一个参数绑定到它所在的实例,在这种情况下第二个参数将是原始函数(partialmethod
的第二个参数)。看起来干净多了。
我在 Python 3.5.1。
我写了一个库,在某些情况下(当文件直接是 运行,即 __name__ == '__main__'
时)我想在 class 之一中装饰某些方法。它应该装饰所有可以创建的实例。我想以一种非侵入性的方式进行,即理想情况下,我库中的 class 不需要任何特殊代码。
经过一段时间,我成功实现了这样的东西,符合我的要求:
def patch(clazz, name, replacement):
def wrap_original(orig):
# when called with the original function, a new function will be returned
# this new function, the wrapper, replaces the original function in the class
# and when called it will call the provided replacement function with the
# original function as first argument and the remaining arguments filled in by Python
def wrapper(*args, **kwargs):
return replacement(orig, *args, **kwargs)
return wrapper
orig = getattr(clazz, name)
setattr(clazz, name, wrap_original(orig))
def replacement_function(orig, self, ... other argumnents ...):
# orig here is the original function, so can be called like this:
# orig(self, ... args ...)
pass
patch(mylib.MyClass, 'method_name', replacemment_function)
令人惊讶的是,这段代码有效,虽然我还没有用 class 方法测试它,但我现在不需要它。它还会修补在修补之前创建的实例,尽管我还不确定它是否好用;d
上面的代码可以说是很难的,写完之后我需要花点时间思考一下它的工作原理,才能写出解释注释。我想要更简单的东西。
问题:Python 库中是否有任何东西可以使这样的代码变得不必要,它已经实现了我正在做的事情,但更好?
您的方法似乎是最符合 Pythonic 的方法。
Gevent,一个使用猴子补丁的流行库,performs monkey patching 几乎与您描述的方式相同。
方法是在查找实例时动态创建的;实例没有所有方法的副本,相反 descriptor protocol 从 class 获取函数并根据需要将这些函数绑定到实例。这就是为什么 monkeypatching class 在这里起作用的原因; instance.method_name
将在执行属性查找时找到 mylib.MyClass.method_name
。
默认库中没有执行您在此处执行的操作的任何内容,不,因为不同的代码可能需要不同的处理委托回旧方法的模式。
您的方法看起来非常接近 how the Mercurial project 支持函数包装,因为原始方法被传递到包装器中。
另一种方法是创建一个 "null" 装饰器函数,然后使用您的条件逻辑在该函数和 "real" 装饰器之间切换:
from decorator_lib import real_decorator
def fake_decorator(fun):
return fun
if __name__ == '__main__':
my_decorator = real_decorator
else:
my_decorator = fake_decorator
# ... elsewhere in the module ...
@my_decorator
def method(self, a, b, c):
pass
# ... finally:
if __name__ == '__main__':
run_self_tests_or_whatever()
这里的一位 post 作者遗憾地删除了 her/his post,他指引我进入 functools
模块。最后,我做出了以下决定:
def replacement(self, orig, ... other arguments ...):
# orig here is the original function, so can be called like this:
# orig(self, ... args ...)
pass
mylib.MyClass.my_method = functools.partialmethod(replacement, mylib.MyClass.my_method)
orig
和 self
参数需要交换位置,因为 partialmethod
将第一个参数绑定到它所在的实例,在这种情况下第二个参数将是原始函数(partialmethod
的第二个参数)。看起来干净多了。