11

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

Adam Hughes
  • 9,853
  • 6
  • 52
  • 89
  • please state at least the text of the error you are getting, then will be clear whether @Philips answer points in the right direction, or there is something else to it. The possibilities are endless... – knitti May 28 '15 at 07:21
  • Your question is really a duplicate of [functools.partial on class method](http://stackoverflow.com/q/16626789); I'll let you pay out the bounty for now. – Martijn Pieters May 28 '15 at 16:19
  • I asked this question 4 months ago, I don't really have the environment anymore to test the solution. The answer provided seems nice though. I didn't put the bounty up though – Adam Hughes May 28 '15 at 16:46
  • @MartijnPieters It was my bounty. I didn't find the functools.partial on a classmethod question but it looks relevant, too. – Rick supports Monica May 28 '15 at 17:01
  • @MartijnPieters Also, funnily enough, this question is actually older than that question, though it didn't have any answers until today! – Rick supports Monica May 28 '15 at 17:04
  • @RickTeachey: that question is two years old, yours only a few months. :-) I added in the `partialmethod()` info into my answer there when I recalled I had posted it and that it could benefit from the extra info, back in March this year. – Martijn Pieters May 28 '15 at 17:09
  • @Martijn Pieters haha whoops! – Rick supports Monica May 29 '15 at 11:40

2 Answers2

13

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:

Community
  • 1
  • 1
Phillip
  • 12,925
  • 24
  • 37
  • 2
    In Python 3, use [`functools.partialmethod()`](https://docs.python.org/3/library/functools.html#functools.partialmethod); it is specifically designed for this usecase. – Martijn Pieters May 28 '15 at 16:17
  • Thanks, I added that and a link to the other question. – Phillip May 29 '15 at 06:32
1

Replace:

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

with:

def newmethod_a(obj, node, **kwargs):
    kwargs.update({"foo": "a"})
    return newmethod(obj, node, **kwargs)
dlask
  • 7,705
  • 1
  • 21
  • 26