Can I replace an existing method of an object in Python? [duplicate]

Can I replace an existing method of an object in Python? [duplicate]



This question already has an answer here:



Q: Is there a way to alter a method of an existing object in Python (3.6)? (By "method" I mean a function that is passed self as an argument.)


self



Example



Let's say I have a class Person having some very useful method SayHi():


Person


SayHi()


class Person(object):
Cash = 100
def HasGoodMood(self):
return self.Cash > 10

def SayHi(self):
if self.HasGoodMood():
print('Hello!')
else:
print('Hmpf.')

>>> joe = Person()
>>> joe.SayHi()
Hello!



As you can see, the response of the person depends on their current mood computed by the method HasGoodMood(). A default person has good mood whenever they have more than 10$ cash on them.


HasGoodMood()



I can easily create a person who does not care about the money and is happy all the time:


>>> joe.HasGoodMood = lambda: True
>>> joe.SayHi()
Hello!
>>> joe.Cash = 0
>>> joe.SayHi()
Hello!



Cool. Notice how Python knows that when using the original implementation of HasGoodMood, it passes silently self as the first argument, but if I change it to lambda: True, it calls the function with no arguments. The problem is: What if I want to change the default HasGoodMood for another function which would also accept self as a parameter?


HasGoodMood


self


lambda: True


HasGoodMood


self



Let's continue our example: what if I want to create a greedy Person who is only happy if they have more than 100$ on them? I would like to do something like:


Person


>>> greedy_jack = Person()
>>> greedy_jack.HasGoodMood = lambda self: self.Cash > 100
TypeError: <lambda>() missing 1 required positional argument: 'self'



Unfortunately, this does not work. Is there some other way to change a method?



Disclaimer: The above example is just for demonstration purposes. I know that I could use inheritance or keep a cash threshold as a property of the Person. But that is not the point of the question.


Person



This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.






@Arne that dupe is pretty out of date, it refers to Python 2 only.

– Daniel Roseman
Sep 12 '18 at 10:12






@DanielRoseman Does it not work in 3 any more?

– Arne
Sep 12 '18 at 10:13






@Arne There is some info relevant to Python 3 there, but it's not obvious. In fact, it's not easy to tell when most of those answers are talking about old-style vs new-style classes.

– PM 2Ring
Sep 12 '18 at 10:17






As far as I'm aware, the top 3 answers in Arne's dupe do apply to python 3. I don't see anything wrong with that dupe target.

– Aran-Fey
Sep 12 '18 at 10:52






@Aran-Fey Fair enough. And Aaron's answer was written with Python 3 in mind.

– PM 2Ring
Sep 12 '18 at 13:22




3 Answers
3



Using some tips from:



Is it possible to change an instance's method implementation without changing all other instances of the same class?



you can do the following, by using the types module to assign a method to the object created without affecting the class. You need to do this because a function does not automatically receive the self object as the first variable, but a method does.


import types

joe = Person()
bob = Person()

joe.SayHi()
>>> Hello!

def greedy_has_good_mood(self):
return self.Cash > 100

joe.HasGoodMood = types.MethodType(greedy_has_good_mood, joe)


joe.SayHi()
>>> Hmpf.
bob.SayHi()

>>> Hello!



When you write a def in a class, and then call it on an instance, that's a method, and the mechanics of method-calling will fill in the self argument when you call it.


def


self



By assigning to HasGoodMood in your instance, you are not putting a new method there, but putting a function into the attribute. You can read the attribute to get the function, and call it, and though that looks like a method call, it's just calling a function that happens to be stored in an attribute. You won't get the self parameter supplied automatically.


HasGoodMood


self



But you already know what self is going to be, since you're assigning this function into one particular object.


self


greedy_jack.HasGoodMood = (lambda self=greedy_jack: self.Cash > 100)



This associates the function argument self with the current value of the variable greedy_jack.


self


greedy_jack



Lambdas in Python can only be one line. If you needed a longer function, you could use a def instead.


def


def greedy_jack_HasGoodMood(self=greedy_jack):
return self.Cash > 100

greedy_jack.HasGoodMood = greedy_jack_HasGoodMood



For a less hacky solution, see Andrew McDowell's answer.






It would be good if you said something about descriptors and method binding, to explain what the OP's actual problem is. However, while I'm not a big fan of your band-aid solution, I don't think it deserves a downvote.

– PM 2Ring
Sep 12 '18 at 10:22



Inheritance is the way to go.



It can be something as simple as:


class Person(object):
Cash = 100
def HasGoodMood(self):
return self.Cash > 10

def SayHi(self):
if self.HasGoodMood():
print('Hello!')
else:
print('Hmpf.')


class newPersonObject(Person):
def HasGoodMood(self):
return self.Cash > 100

>>> greedy = newClassPerson()
>>> greedy.SayHi()
hmpf



When you do greedy_jack.HasGoodMood = lambda self: self.Cash > 100 you're somewhat doing the same thing. You're only overriding greedy_jacks attributes. Using the way mentioned about, you can create greedy people, happy people, forever unhappy people, hippies etc.


greedy_jack.HasGoodMood = lambda self: self.Cash > 100


greedy_jacks



A better option in my opinion would be to accept cash a parameter while defining the object. Hence, you can dynamically make people greedy or normal. (not tested)


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

def HasGoodMood(self, has_money):
return has_money > self.cash

def SayHi(self, has_money):
if self.HasGoodMood(has_money):
print('Hello!')
else:
print('Hmpf.')

>>> joe = Person(100)
>>> joe.SayHi(150)
Hello!
>>> greedy_joe = Person(200)
>>> greedy_joe.SayHi(150)
Hmpf






See the disclaimer: These are correct and definitely preferable solutions for a real scenario, however, the question is specifically not asking for them.

– Jan Kukacka
Sep 12 '18 at 10:22

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)