Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monkey patching with a partial function [duplicate]

I'm trying to monkeypatch a method on SomeClass from an imported package:

from somepackage import SomeClass

def newmethod(obj, node, **kwargs):
    """ """

SomeClass.oldmethod = newmethod

Where obj and node are in the default call signature of SomeClass.oldmethod:

class SomeClass(object):

    def oldmethod(obj, node):
        """ """  

I'm aware that monkeypatching is not good practice, but we need a workaround while we fix some issues that otherwise can't be tackled. The above approach works FINE, but we'd like to use partial functions to do this. For example:

from functools import partial
newmethod_a = partial(newmethod, foo='a')
newmethod_b = partial(newmethod, foo='b')

The partial function is being called because we need to pass different **kwargs. But when I try to overload now:

SomeClass.oldmethod = newmethod_a

I get an error related to the number of arguments passed, but it's very specific to my problem so pasting it might not be helpful... The error I think is related to the call signature of oldmethod taking two positional arguments (obj, node), and my partial functions aren't passing a reference to the obj and node correctly. I've tried different constructions like:

newmethod_a = partial(SomeClass.newmethod, foo='a')

I'm sorry that I can't produce a minimal working example. I hoped maybe an expert would just recognize this issue from experience and tell me if what I'm attempting is even possible within the scope of partial.

Thanks

like image 974
Adam Hughes Avatar asked Jan 28 '15 05:01

Adam Hughes


1 Answers

Here's a quick example:

from functools import partial

class foo(object):
    def bar(self, pos1, **kwargs):
        print("bar got self=%r, pos1=%r, kwargs=%r" % (self, pos1, kwargs))

foo.bar = partial(foo.bar, qux=1)
baz = foo()
baz.bar(1) # Fails...

This fails with a TypeError. The reason for this is that baz.bar is a bound method which expects its first argument to be a foo instance, but that the partial object is not, and hence Python will not add self for you when you call baz.bar. (This is not entirely correct, but the true reason is quite technical. See the Descriptor How-To linked below.) Calling baz.bar(baz, 1) would work. To work around this, you'll have to make foo.bar a method again:

import types

# In Python 2:
foo.bar = types.MethodType(partial(foo.bar.__func__, qux=1), None, foo)
# Method that is compatible with both Python 2 and 3:
foo.bar = types.MethodType(partial(foo.bar, qux=1), foo)
# Python 3 only:
from functools import partialmethod
foo.bar = partialmethod(foo.bar, qux=1)

baz = foo()
baz.bar(1) # Works!

See also:

  • Closely related: Adding a Method to an Existing Object, functools.partial on class method
  • Loosely related, on functions vs. methods and when/how methods are bound:

    • Documentation on Method Objects
    • How-To guide for descriptors
    • Bind an unbound method
like image 168
Phillip Avatar answered Sep 30 '22 20:09

Phillip