3

I know how fierce the SO community is so I'll try my best to keep the question minimal, complete and verifiable.

What I simply want to know is can monkey patching be used to replace the definition of an existing function?

for example:

class A():

    def foo():
       print '2'

def foo():
    print '5'

A.foo = foo

This way doesn't seem to work also as to why I don't just add a new function instead of replacing an existing one, I call these functions in other classes and it is my understanding that monkey patching adds those functions at run-time and I need my python code to run on an Apache spark server which throws an error deeming the calls to that function unreferenced.

So please be nice and help me out or suggest a work around. Thanks.

Edit: The goal of the code is to print 5 when A.foo is called.

Vivek Shankar
  • 770
  • 1
  • 15
  • 37
  • Certainly not with `A.foo = print '5'`, have you tried `A.foo = lambda: print('5')`? (Note that with Python 2 you need to do `from __future__ import print_function`) – tobias_k Mar 17 '17 at 11:22
  • You can see how it's done in [this answer](http://stackoverflow.com/a/2982/1222951). – Aran-Fey Mar 17 '17 at 11:23
  • Your code looks quite garbled. Do you want to assign the assignment of a new function called `foo` to `A` when you are calling `foo`? – Sören Titze Mar 17 '17 at 11:23
  • @tobias_k Thanks for the quick reply could you suggest something for python 2? – Vivek Shankar Mar 17 '17 at 11:24
  • @Nessuno That's what I was intending to get across. In short, I want `A.foo` to print 5 – Vivek Shankar Mar 17 '17 at 11:26
  • @Rawing I had previously gone through that answer but I don't want to assign the function to an instance but to the class itself so that answer doesn't really apply to my question. Thanks nevertheless. – Vivek Shankar Mar 17 '17 at 11:32
  • @tobias_k Could you take a look at @Rawing 's link. Is it really necessary to use `lambda` here? – Vivek Shankar Mar 17 '17 at 11:36
  • My apologies the earlier example wasn't correct I still intend to print 5 though. – Vivek Shankar Mar 17 '17 at 12:07

2 Answers2

3

Your only problem is that you aren't defining foo correctly in the first place. It needs to take an explicit argument for the instance calling it.

class A(object):
    def __init__(self)
        self.x = 2

    def foo(self):
        print(self.x)

def foo(this):
    print(this.x + 3)

A.foo = foo

a = A()
a.foo()  # outputs 5 in Python 2 and Python 3

In a very real sense, monkey patching is how classes are created in the first place. A class statement is almost just syntactic sugar for the following code:

def foo(self):
    print(self.x)
A = type('A', (object,), {'foo': foo})
del foo

It's not too much of a simplification to image the definition of type being something like

def type(name, bases, d):
    new_class = magic_function_to_make_a_class()
    new_class.name = name
    new_class.bases = bases
    for k, v in d.items():
        setattr(new_class, k, v)
    return new_class
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Yes, this worked for me. I'm new to python, could you please explain why the argument `self` is so important here? Apparently the functions can even have different number of parameters. – Vivek Shankar Mar 17 '17 at 12:25
  • Thanks, I get it now `self` is kinda like the `this` of python. – Vivek Shankar Mar 17 '17 at 12:31
  • 3
    The name `self` isn't special; it's just a convention. You could use `this` if you really wanted to, and it doesn't need to be the same name in both functions defined here (I'll edit the answer to demonstrate). What is important is that the *first* argument to a function used as an instance method will be a reference to the calling object. `a.foo()` is almost exactly the same as `A.foo(a)`. – chepner Mar 17 '17 at 12:34
2

I hope I understand what you are trying to do here. This would work in Python 3:

class A():

  def foo():
     print('2')

def foo():
  A.foo = lambda: print('5')

A.foo() # Print '2'
foo()   # Assign the new method
A.foo() # Prints '5'

In Python 2 however there are several caveats.

So you have to do it like this:

from __future__ import print_function

class A():

  def foo():
    print('2')

def foo():
  A.foo = lambda: print('5')

A.foo.__func__() # Print '2'
foo()   # Assign the new method
A.foo.__func__() # Prints '5'

Edit: After seeing your question in the comment I think you actually want something different. Which is this:

class A():

    def foo(self):
       print '2'

def foo(self):
  print '5'

a = A()
a.foo() # Print '2'
A.foo = foo   # Assign the new method
a.foo() # Prints '5'

This works just fine in Python 2.

The self is a reference to the current instance the method is bound to. It is not used when you just call something like print which access any properties or methods attached to that instance. But for a different case please have a look at the following example:

class A():

    msg = "Some message"

    def foo(self):
       print self.msg


def bar(self):
  self.msg = "Some other message"

a = A()
a.foo() # Print old msg
A.bar = bar   # Assign the new method
a.bar() # Assigns new message
a.foo() # Prints new message

Also as chepner points out in a comment under his post:

The name self isn't special; it's just a convention. You could use this if you really wanted to, and it doesn't need to be the same name in both functions defined here. What is important is that the first argument to a function used as an instance method will be a reference to the calling object. a.foo() is almost exactly the same as A.foo(a)

Community
  • 1
  • 1
Sören Titze
  • 985
  • 1
  • 12
  • 31
  • Yes, that is what @tobias_k suggested in the comments, but lambda becomes a little inconvenient when defining complex functions. I'm following [this](https://www.codementor.io/jadianes/building-a-web-service-with-apache-spark-flask-example-app-part2-du1083854) tutorial and it does something similar to my example in the question, does that work in python 3? Could it be that the tutorial is in python 3?Since it's isn't explicitly mentioned. – Vivek Shankar Mar 17 '17 at 12:04
  • @VivekShankar This seems of not great importance. As far as I can see this tutorial actually attaches **bound** methods to the class so that they can be called via `a = new A(); a.foo()`. The giveaway is that they actually provide a `self` parameter in the method definition. – Sören Titze Mar 17 '17 at 12:07
  • Agreed, but following the way shown in the tutorial and the conventional monkey patching I get `unresolved reference` in PyCharm when I do `a = new A()` and then call it as `a.foo()` . – Vivek Shankar Mar 17 '17 at 12:15
  • Sorry I messed up. It should be `a = A()` please see my latest edit. – Sören Titze Mar 17 '17 at 12:15
  • 2
    The fact that `print` is a statement in Python 2 just means you can't just a `lambda` expression to define the function. You can still use the `def` statement to wrap `print`, and assign *that* function to `A.foo`. – chepner Mar 17 '17 at 12:19
  • @Nessuno Yes, the recent edit is exactly what I want to achieve, could explain why adding `self` argument to the two functions is important here? – Vivek Shankar Mar 17 '17 at 12:19
  • @chepner Yes you are right. It was just that he seemed to be trying to write a class function that attached an unbound method of the same name. That's why I asked if he really wanted to do that in comment to his question. VivekShankar please have a look at the latest edit. – Sören Titze Mar 17 '17 at 12:23
  • @Nessuno Just saw your latest edit, that made everything crystal. Now if my instance is defined in another python file and the reference to `a.bar()` gives _unresolved reference_ how can I fix this? – Vivek Shankar Mar 17 '17 at 12:38
  • You have to import it. Have a look here: http://stackoverflow.com/questions/4142151/python-how-to-import-the-class-within-the-same-directory-or-sub-directory – Sören Titze Mar 17 '17 at 12:39
  • @Nessuno I havent added the empty `__init__.py` yet but I'm able to import from that directory and `a = A()` works just fine it is only when I call a function added by the method you suggested above that i get the _unresolved reference error_ particularly at 'a.foo()'. – Vivek Shankar Mar 17 '17 at 12:48
  • My guess is that you moved the code were you are actually monkey patching to a different file. And that file is never executed. Please note that Python is a script language which means that it is not compiled like Java or C. What I guess happens is that the Interpreter has no idea of the method you are trying to append. This means you could either keep all the method definitions you are trying to append in the same file or import the file including functions. – Sören Titze Mar 17 '17 at 12:53
  • @Nessuno Thanks, I understand it much better now. – Vivek Shankar Mar 17 '17 at 18:27