1

I looked at the following posts and blogs which explain to do this with a function. I wonder if its even legal to do the same for a class or object method? Will appreciate answers which work for Python2 (though its good to know how it works for Python3 as well).

Sources from stackoverflow:

Override module method where from...import is used and Override a method at instance level

blogs with similar content:

https://tryolabs.com/blog/2013/07/05/run-time-method-patching-python/ http://igorsobreira.com/2011/02/06/adding-methods-dynamically-in-python.html

This code illustrates my intention and what I tried:

class Person:
        def __init__(self, name):
            self.name = name


    class OtherPerson:
        def __init__(self, name):
            self.name = name 

        def do_something(self):
            print self.name + '$$'

  p = Person('alpha')
  p.do_something = OtherPerson.do_something
  # TypeError: unbound method do_something() must be called with 
  OtherPerson #instance as first argument (got nothing instead)
  p.do_something()
  op = OtherPerson('beta')
  p.do_something = op.do_something

  # output: 'beta$$' I would like to get 'alpha$$'
  p.do_something()

Tried following suggestion from @khelwood:

Person.do_something = OtherPerson.do_something
#TypeError: unbound method do_something() must be called with OtherPerson instance as first argument (got nothing instead)
# Works if OtherPerson.do_something is staticmethod
p.do_something()
S.Arora
  • 263
  • 3
  • 7
  • You can do `Person.do_something = OtherPerson.do_something`, if that is what you're asking – khelwood Jan 11 '18 at 15:47
  • I'm sure there's no law on the books in any country making it *illegal*… ;) – deceze Jan 11 '18 at 15:52
  • @khelwood That feels like it would work, but unfortunately it doesnt. Got same error: **TypeError: unbound method do_something() must be called with OtherPerson instance as first argument (got nothing instead)** – S.Arora Jan 11 '18 at 16:02
  • Then I guess you're using Python 2. – khelwood Jan 11 '18 at 16:07
  • @khelwood : Correct, and making Person and OtherPerson extend from object doesn't help either. So then is it *not allowed* in python 2 and *allowed* in Python 3? That addresses the *legal* part of the question I guess then! (**at**: deceze) – S.Arora Jan 11 '18 at 16:11

2 Answers2

3

The key is to pass the right arguments to the method. Let's have a closer look at the error you're getting in the first place:

TypeError: unbound method do_something() must be called with 
OtherPerson #instance as first argument (got nothing instead)

When you look at OtherPerson.do_something, it's clear that it's expecting an instance as its first parameter. So now p.do_something refers to OtherPerson.do_something, it needs that first parameter. Therefore, a correct call, in the current state, would be:

p.do_something(p)

Of course, this is not really nice, since you have to specify the instance twice. That's because the method is now unbound: it does not know of the instance on which it is called, ie it does not know self.

The solution I'm proposing consists in making p.do_something refer to a function that calls OtherPerson.fo_something with p as first argument.


Let's have two classes, Foo and Bar, defined as follow:

class Foo:
    def __init__(self, x):
        self.x = x

    def speak(self):
        print("Foo says:", self.x)


class Bar:
    def __init__(self, x):
        self.x = x

    def speak(self):
        print("Bar says:", self.x)

Suppose you have a foo instance from the Foo class. Now, you want to dynamically override its speak method, so that it calls Bar's instead. You can simply reassign foo.speak to a function that calls Bar.speak.

>>> foo = Foo(2)
>>> foo.speak()
Foo says: 2
>>> foo.speak = lambda: Bar.speak(foo)
>>> foo.speak()
Bar says: 2

You can make it even more generic. For the sake of example, let's write a function that takes an instance, a method name, and a target class, and overrides the instance's matching method with the target class':

def override(instance, method_name, target_class):
    class_method = getattribute(target_class, method_name)

    def new_method(*args, **kwargs):
        return class_method(instance, *args, **kwargs)

    setattribute(instance, method_name, new_method)

You can observe the same expected behaviour:

>>> foo = Foo(2)
>>> override(foo, "speak", Bar)
>>> foo.speak()
Bar says: 2
Right leg
  • 16,080
  • 7
  • 48
  • 81
  • Thanks for your answer an very well explained indeed. This doesn't seem to work with Python2 though! Can you please explain how to modify this to work for Python 2.7? Just the initial example, I get the last generic part with override method. – S.Arora Jan 11 '18 at 19:10
  • Cont.. the error is now: TypeError: unbound method speak() must be called with Bar instance as first argument (got Foo instance instead) – S.Arora Jan 11 '18 at 19:47
  • @S.Arora It looks like another error of Python 2: the duck-typing is flawed, since a type check is performed. If `Bar.do_something` unbound method requires a `Bar` instance, there's no choice but to force a cast of `foo` into a `Bar` instance, which obviously requires the writing of a conversion function. Comparing with how easy it is to do in Python 3, I'd say it's not feasible in a purely dynamic way in Python 2. – Right leg Jan 11 '18 at 23:15
  • That's reasonable, I'll accept your answer since it works for Python3 anyways. – S.Arora Jan 12 '18 at 16:22
0

This can be done if the OtherPerson.do_something is a staticmethod:

class Person(object):
    def __init__(self, name):
        self.name = name


class OtherPerson(object):
    def __init__(self, name):
        self.name = name

    @staticmethod
    def do_something(self):
        print self.name + '$$'


p = Person('alpha')
Person.do_something = OtherPerson.do_something

p.do_something() #alpha$$
S.Arora
  • 263
  • 3
  • 7