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.)
Let's say I have a class
Person having some very useful method
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.
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?
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:
>>> 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.
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.
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.
But you already know what
self is going to be, since you're assigning this function into one particular object.
greedy_jack.HasGoodMood = (lambda self=greedy_jack: self.Cash > 100)
This associates the function argument
self with the current value of the variable
Lambdas in Python can only be one line. If you needed a longer function, you could use a
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.