Is it possible to change an instance's method implementation without changing all other instances of the same class? [duplicate]
Asked Answered
M

6

37

I do not know python very much (never used it before :D), but I can't seem to find anything online. Maybe I just didn't google the right question, but here I go:

I want to change an instance's implementation of a specific method. When I googled for it, I found you could do it, but it changes the implementation for all other instances of the same class, for example:

def showyImp(self):
    print self.y

class Foo:
    def __init__(self):
        self.x = "x = 25"
        self.y = "y = 4"

    def showx(self):
        print self.x

    def showy(self):
         print "y = woohoo"

class Bar:
    def __init__(self):
        Foo.showy = showyImp
        self.foo = Foo()

    def show(self):
        self.foo.showx()
        self.foo.showy()

if __name__ == '__main__':
    b = Bar()
    b.show()
    f = Foo()
    f.showx()
    f.showy()

This does not work as expected, because the output is the following:

x = 25

y = 4

x = 25

y = 4

And I want it to be:

x = 25

y = 4

x = 25

y = woohoo

I tried to change Bar's init method with this:

def __init__(self):
    self.foo = Foo()
    self.foo.showy = showyImp

But I get the following error message:

showyImp() takes exactly 1 argument (0 given)

So yeah... I tried using setattr(), but seems like it's the same as self.foo.showy = showyImp.

Any clue? :)

Magel answered 30/10, 2009 at 1:42 Comment(2)
Is there a particular reason you're not doing this with inheritance?Gravely
Yes, because I have very limited access to the created object. I'm not the one creating it (hence the non-possibility of inheritance), and I cannot modify the original object's code (also because it would not make sense anyway, I need to change the method only in a context that is completely different from what the object is initaly intended for :).Magel
C
66

Since Python 2.6, you should use the types module's MethodType class:

from types import MethodType

class A(object):
    def m(self):
        print 'aaa'

a = A()

def new_m(self):
    print 'bbb'

a.m = MethodType(new_m, a)

As another answer pointed out, however, this will not work for 'magic' methods of new-style classes, such as __str__().

Crenelate answered 18/8, 2012 at 22:15 Comment(2)
Thank you! The accepted answer doesn't work in Python 3, and the documentation page doesn't make it very clear how to use MethodType.Held
Its not a huge deal, I guess (though if you know how to fix this, then I'd appreciate the help), but... This overwrites a method of an instantiated object, not the class itself... so when a new instance of the class is created, you have to run this code to change the method, as opposed to having the method change in the physical class creator.Roti
S
31

This answer is outdated; the answer below works with modern Python

Everything you wanted to know about Python Attributes and Methods.

Yes, this is an indirect answer, but it demonstrates a number of techniques and explains some of the more intricate details and "magic".

For a "more direct" answer, consider python's new module. In particular, look at the instancemethod function which allows "binding" a method to an instance -- in this case, that would allow you to use "self" in the method.

import new
class Z(object):
  pass
z = Z() 
def method(self):
  return self
z.q = new.instancemethod(method, z, None)
z is z.q()  # true
Swick answered 30/10, 2009 at 2:1 Comment(3)
Does this have a Python 3 equivalent?Stylistic
The python 3 equivalent for z.q = new.instancemethod(method, z, None) is z.q = types.MethodType(method, z). Remember to import types instead of new.Selma
Link is broken.Rubalcava
C
10

If you ever need to do it for a special method (which, for a new-style class -- which is what you should always be using and the only kind in Python 3 -- is looked up on the class, not the instance), you can just make a per-instance class, e.g....:

self.foo = Foo()
meths = {'__str__': lambda self: 'peekaboo!'}
self.foo.__class__ = type('yFoo', (Foo,), meths)

Edit: I've been asked to clarify the advantages of this approach wrt new.instancemethod...:

>>> class X(object): 
...   def __str__(self): return 'baah'
... 
>>> x=X()
>>> y=X()
>>> print x, y
baah baah
>>> x.__str__ = new.instancemethod(lambda self: 'boo!', x)
>>> print x, y
baah baah

As you can see, the new.instancemethod is totally useless in this case. OTOH...:

>>> x.__class__=type('X',(X,),{'__str__':lambda self:'boo!'})
>>> print x, y
boo! baah

...assigning a new class works great for this case and every other. BTW, as I hope is clear, once you've done this to a given instance you can then later add more method and other class attributes to its x.__class__ and intrinsically affect only that one instance!

Cumberland answered 30/10, 2009 at 3:6 Comment(4)
What would be the advantage of this method compared to using new. instancemethod?Magel
The latter approach just doesn't work for special methods in new-style classes, as I said, while this one is general. Let me edit the answer to show an example.Cumberland
Ah ok I see! Thanks a lot for the info. It's interesting :).Magel
Some archeology!... So does this approach still work in python 3?Thanksgiving
D
5

If you're binding to the instance, you shouldn't include the self argument:

>>> class Foo(object):
...     pass
... 
>>> def donothing():
...     pass
... 
>>> f = Foo()
>>> f.x = donothing
>>> f.x()
>>> 

You do need the self argument if you're binding to a class though:

>>> def class_donothing(self):
...     pass
... 
>>> foo.y = class_donothing
>>> f.y()
>>> 
Dramaturgy answered 30/10, 2009 at 1:49 Comment(2)
I need to bind to the instance, and I need access to "self", and I cannot modify the existing call-procedure of the method, which is why I cannot do self.foo.showy(self.foo)Magel
For what its worth, this answer was helpful to me :)Roti
A
0

Your example is kind of twisted and complex, and I don't quite see what it has to do with your question. Feel free to clarify if you like.

However, it's pretty easy to do what you're looking to do, assuming I'm reading your question right.

class Foo(object):
    def bar(self):
        print('bar')

def baz():
    print('baz')

In an interpreter ...

>>> f = Foo()
>>> f.bar()
bar
>>> f.bar = baz
>>> f.bar()
baz
>>> g = Foo()
>>> g.bar()
bar
>>> f.bar()
baz
Alcheringa answered 30/10, 2009 at 1:58 Comment(1)
The problem with this solution is that it does not use self (as a parameter), which I do need, and I can't do f.bar(f), mainly because I don't want to (it looks very anti-pattern like).Magel
D
0

Do Not Do This.

Changing one instance's methods is just wrong.

Here are the rules of OO Design.

  1. Avoid Magic.

  2. If you can't use inheritance, use delegation.

That means that every time you think you need something magic, you should have been writing a "wrapper" or Facade around the object to add the features you want.

Just write a wrapper.

Dacia answered 30/10, 2009 at 10:26 Comment(2)
@naixn: It's your question. You own the question. You can update the question. If you update the question to contain ALL the facts, you do not have to resort to out-of-band messages. Please update the question with the elaborate justification for a fundamentally flawed design. Your explanation amounts to "I don't want to wrap and delegate." (1) Please UPDATE your question, (2) Please reconsider your flawed design.Dacia
What if you need a third-party library to behave very slightly differently? In my particular case, I need one specific instance of Logger to append a bit of data to each log message that the library sends to the logger's info() method. Would it be possible to use wrap-and-delegate for that?Held

© 2022 - 2024 — McMap. All rights reserved.