3

I am confused with following difference. Say I have this class with some use case:

class C:
    def f(self, a, b, c=None):
        print(f"Real f called with {a=}, {b=} and {c=}.")


my_c = C()
my_c.f(1, 2, c=3)  # Output: Real f called with a=1, b=2 and c=3.

I can monkey patch it for purpose of testing like this:

class C:
    def f(self, a, b, c=None):
        print(f"Real f called with {a=}, {b=} and {c=}.")


def f_monkey_patched(self, *args, **kwargs):
    print(f"Patched f called with {args=} and {kwargs=}.")


C.f = f_monkey_patched
my_c = C()
my_c.f(1, 2, c=3)  # Output: Patched f called with args=(1, 2) and kwargs={'c': 3}.

So far so good. But I would like to patch only one single instance and it somehow consumes first argument:

class C:
    def f(self, a, b, c=None):
        print(f"Real f called with {a=}, {b=} and {c=}.")


def f_monkey_patched(self, *args, **kwargs):
    print(f"Patched f called with {args=} and {kwargs=}.")


my_c = C()
my_c.f = f_monkey_patched
my_c.f(1, 2, c=3)  # Output: Patched f called with args=(2,) and kwargs={'c': 3}.

Why has been first argument consumed as self instead of the instance itself?

Roman Pavelka
  • 3,736
  • 2
  • 11
  • 28

3 Answers3

3

Functions in Python are descriptors; when they're attached to a class, but looked up on an instance of the class, the descriptor protocol gets invoked, producing a bound method on your behalf (so my_c.f, where f is defined on the class, is distinct from the actual function f you originally defined, and implicitly passes my_c as self).

If you want to make a replacement that shadows the class f only for a specific instance, but still passes along the instance as self like you expect, you need to manually bind the instance to the function to create the bound method using the (admittedly terribly documented) types.MethodType:

from types import MethodType  # The class implementing bound methods in Python 3

# ... Definition of C and f_monkey_patched unchanged

my_c = C()
my_c.f = MethodType(f_monkey_patched, my_c)  # Creates a pre-bound method from the function and
                                             # the instance to bind to

Being bound, my_c.f will now behave as a function that does not accept self from the caller, but when called self will be received as the instance bound to my_c at the time the MethodType was constructed.


Update with performance comparisons:

Looks like, performance-wise, all the solutions are similar enough as to be irrelevant performance-wise (Kedar's explicit use of the descriptor protocol and my use of MethodType are equivalent, and the fastest, but the percentage difference over functools.partial is so small that it won't matter under the weight of any useful work you're doing):

>>> # ... define C as per OP
>>> def f_monkey_patched(self, a):  # Reduce argument count to reduce unrelated overhead
...     pass

>>> from types import MethodType
>>> from functools import partial
>>> partial_c, mtype_c, desc_c = C(), C(), C()
>>> partial_c.f = partial(f_monkey_patched, partial_c)
>>> mtype_c.f = MethodType(f_monkey_patched, mtype_c)
>>> desc_c.f = f_monkey_patched.__get__(desc_c, C)
>>> %%timeit x = partial_c  # Swapping in partial_c, mtype_c or desc_c
... x.f(1)
...

I'm not even going to give exact timing outputs for the IPython %%timeit magic, as it varied across runs, even on a desktop without CPU throttling involved. All I could say for sure is that partial was reliably a little slower, but only by a matter of ~1 ns (the other two typically ran in 56-56.5 ns, the partial solution typically took 56.5-57.5), and it took quite a lot of paring of extraneous stuff (e.g. switching from %timeit reading the names from global scope causing dict lookups to caching to a local name in %%timeit to use simple array lookups) to even get the differences that predictable.

Point is, any of them work, performance-wise. I'd personally recommend either my MethodType or Kedar's explicit use of descriptor protocol approach (they are identical in end result AFAICT; both produce the same bound method class), whichever one looks prettier to you, as it means the bound method is actually a bound method (so you can extract .__self__ and .__func__ like you would on any bound method constructed the normal way, where partial requires you to switch to .args[0] and .func to get the same info).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 1
    For the nitpicky: On modern CPython, "the descriptor protocol gets invoked, producing a bound method on your behalf" is a bit of a lie; it avoids creating bound methods in many cases, because it can recognize the common scenario and the interpreter itself fudges things, passing along the `self` directly to the underlying function without actually creating a bound method object when you perform both lookup and call in a single expression without storing the bound method. But when that optimization doesn't apply, a true bound method is needed. – ShadowRanger Aug 30 '22 at 17:35
2

You can convert the function to bound method by calling its __get__ method (since all function as descriptors as well, thus have this method)

def t(*args, **kwargs):
    print(args)
    print(kwargs)

class Test():
    pass
Test.t = t.__get__(Test(), Test) # binding to the instance of Test

For example

Test().t(1,2, x=1, y=2)
(<__main__.Test object at 0x7fd7f6d845f8>, 1, 2)
{'y': 2, 'x': 1}

Note that the instance is also passed as an positional argument. That is if you want you function to be instance method, the function should have been written in such a way that first argument behaves as instance of the class. Else, you can bind the function to None instance and the class, which will be like staticmethod.

Test.tt = t.__get__(None, Test)
Test.tt(1,2,x=1, y=2)
(1, 2)
{'y': 2, 'x': 1}

Furthermore, to make it a classmethod (first argument is class):

Test.ttt = t.__get__(Test, None) # bind to class
Test.ttt(1,2, x=1, y=2)
(<class '__main__.Test'>, 1, 2)
{'y': 2, 'x': 1}
Kedar U Shet
  • 538
  • 2
  • 11
  • Nice alternative to using `types.MethodType` explicitly. I'm a little leery of manually calling special methods (it feels... unclean; when at all possible I prefer to invoke them implicitly, partially because there are subtle distinctions in how they're looked up when called implicitly [usually looked up directly on the class efficiently without checking the instance] vs. explicitly [looked up on the instance first, which is slower and risks incorrect behavior where it might read an instance version incorrectly]), but it's a reasonable use here. – ShadowRanger Aug 30 '22 at 16:16
  • I think you should be demonstrating these uses on an instance of `Test` though, not `Test` itself (the OP's goal is to monkey-patch an instance with a new bound method; this is attaching bound methods to classes). – ShadowRanger Aug 30 '22 at 16:26
1

When you do C.f = f_monkey_patched, and later instantiate an object of C, the function is bound to that object, effectively doing something like

obj.f = functools.partial(C.f, obj)

When you call obj.f(...), you are actually calling the partially bound function, i.e. f_monkey_patched(obj, ...)

On the other hand, doing my_c.f = f_monkey_patched, you assign the function as-is to the attribute my_c.f. When you call my_c.f(...), those arguments are passed to the function as-is, so self is the first argument you passed, i.e. 1, and the remaining arguments go to *args

Pranav Hosangadi
  • 23,755
  • 7
  • 44
  • 70
  • 2
    Yep, I saw you edited it just before I hit submit on my comment. :-) I will say, while this works (and I don't hate it or anything), I do think, as a matter of behaving as similarly as possible to the non-patched code, it's better to make a true bound method (whether by my method or Kedar's doesn't really matter) than to simulate one slightly imprecisely with `functools.partial`. `functools.partial` can't be manipulated the same way (it has no concept of `.__self__` or `.__func__`, so you have to treat it differently), and unlike bound methods, it has no core interpreter optimizations. – ShadowRanger Aug 30 '22 at 16:22
  • @ShadowRanger I agree, I figured `partial` might make for a better analogy since I learned about `partial` before I learned about `MethodType`, and that was good enough to explain why OP sees the behavior they see. I planned on adding a "what _actually_ happens is `MethodType`" section, but you added an answer before I could get to it :) – Pranav Hosangadi Aug 30 '22 at 17:31