I don't seem to be able to monkey patch a __call__
method of class instance (and yes, I want to patch just single instances, not all of them).
The following code:
class A(object):
def test(self):
return "TEST"
def __call__(self):
return "EXAMPLE"
a = A()
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
a.__call__ = lambda : "example"
a.test = lambda : "test"
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
print(a())
print("Explicit call: {0}".format(a.__call__()))
print(a.test())
Outputs this:
call method: <bound method A.__call__ of <__main__.A object at 0x7f3f2d60b6a0>>
test method: <bound method A.test of <__main__.A object at 0x7f3f2d60b6a0>>
call method: <function <lambda> at 0x7f3f2ef4ef28>
test method: <function <lambda> at 0x7f3f2d5f8f28>
EXAMPLE
Explicit call: example
test
While I'd like it to output:
...
example
Explicit call: example
test
How do I monkeypatch __call__()
? Why I can't patch it the same way as I patch other methods?
While this answer tells how to do it (supposedly, I haven't tested it yet), it doesn't explain the why part of the question.
Monkey patching is reopening the existing classes or methods in class at runtime and changing the behavior, which should be used cautiously, or you should use it only when you really need to. As Python is a dynamic programming language, Classes are mutable so you can reopen them and modify or even replace them.
Monkey patching is a technique used to dynamically update the behavior of a piece of code at run-time. A monkey patch (also spelled monkey-patch, MonkeyPatch) is a way to extend or modify the runtime code of dynamic languages (e.g. Smalltalk, JavaScript, Objective-C, Ruby, Perl, Python, Groovy, etc.)
Monkey patching is a technique that allows you to alter the behavior of objects at runtime. Even though it can be a very useful feature, it can also make your code much harder to understand and debug, and therefore you have to be careful with how you implement monkey patching.
In Python, the term monkey patch refers to dynamic (or run-time) modifications of a class or module. In Python, we can actually change the behavior of code at run-time. We use above module (monk) in below code and change behavior of func() at run-time by assigning different value.
So, as J.J. Hakala commented, what Python really does, is to call:
type(a).__call__(a)
as such, if I want to override the __call__
method, I must override the __call__
of a class, but if I don't want to affect behaviour of other instances of the same class, I need to create a new class with the overriden __call__
method.
So an example of how to override __call__
would look like this:
class A(object):
def test(self):
return "TEST"
def __call__(self):
return "EXAMPLE"
def patch_call(instance, func):
class _(type(instance)):
def __call__(self, *arg, **kwarg):
return func(*arg, **kwarg)
instance.__class__ = _
a = A()
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
patch_call(a, lambda : "example")
a.test = lambda : "test"
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
print("{0}".format(a()))
print("Explicit a.__call__: {0}".format(a.__call__()))
print("{0}".format(a.test()))
print("Check instance of a: {0}".format(isinstance(a, A)))
Running it produces following output:
call method: <bound method A.__call__ of <__main__.A object at 0x7f404217a5f8>>
test method: <bound method A.test of <__main__.A object at 0x7f404217a5f8>>
call method: <bound method patch_call.<locals>._.__call__ of <__main__.patch_call.<locals>._ object at 0x7f404217a5f8>>
test method: <function <lambda> at 0x7f404216d048>
example
Explicit a.__call__: example
test
Check instance of a: True
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception:
>>> class C: ... pass ... >>> c = C() >>> c.__len__ = lambda: 5 >>> len(c) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object of type 'C' has no len()
Source: https://docs.python.org/3/reference/datamodel.html#special-lookup
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With