Messing With Self
First, the boring case where classes are classes and instances are instances.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def __init__(self, msg): | |
self.msg = msg | |
def say_stuff(self): | |
print("Say stuff message is: {}\n\n".format(self.msg)) | |
# normal call and instantiation | |
ob1 = MyClass("Hello there, first example.") | |
ob1.say_stuff() |
Nothing new there. But what is with self? What's so special about it? Let's rebel. Everywhere self is shall be replaced with the word her.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def __init__(her, msg): | |
her.msg = msg | |
def say_stuff(her): | |
print("Say stuff message is: {}\n\n".format(her.msg)) | |
# normal call and instantiation | |
ob1 = MyClass("Hello there, This is me.") | |
ob1.say_stuff() |
It turns out self is not that special after all. It's just a convention for distinguishing the instance of a class from the class itself. Strictly speaking, self could be this or alice or bob or the gender pronoun of your choice. Politically correct Python for the win!
But seriously, did you ever stop to think about how strange it is when it comes to self and how calls method calls actually work?
ob1.say_stuff()
The ob1 in this case looks like it's the self that say_stuff refers to. Let's twist that call around a little.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
say_stuff = MyClass.say_stuff | |
say_stuff(ob1) |
Okay, so there's that. Now, be sure of this. The self term absolutely refers to an instance of the class it's being used in ... right? You have to wonder sometimes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def say_stuff(self): | |
print("Say stuff message is: {}\n\n".format(self.msg)) | |
class OtherClass(object): | |
def __init__(self, msg): | |
self.msg = msg | |
other_ob = OtherClass("Hello from the other_ob instance") | |
MyClass.say_stuff(other_ob) |
So much for that idea. At any rate, it's safe to say we've abused self enough for now.
Time For Some Actual Monkey Patching
Here, we play a game about filling in the missing pieces. What if we had a starting class without an __init__. To make it more interesting, let's NOT set up another class through which to provide instance variables. Something like this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def say_stuff(self): | |
print("Say stuff message is: {}\n\n".format(self.msg)) |
And who would have thunk. We can actually make this work in spite of the fact that the instance was born without a msg. Time for some setattr() surgery.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def say_stuff(self): | |
print("Say stuff message is: {}\n\n".format(self.msg)) | |
ob = MyClass() | |
setattr(ob, "msg", "Here is a message from a setattr") | |
ob.say_stuff() |
Oh heck, let's go gangbusters and just monkey patch a bare naked class together. Here's what that ends up looking like. Heck, we'll do up the instance too. Here it goes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
pass | |
ob = MyClass() | |
def say_stuff(self): | |
print("The say stuff message here is: {}\n\n".format(self.msg)) | |
setattr(MyClass, "say_stuff", say_stuff) | |
setattr(ob, "msg", "Patched in message for patched in class") | |
ob.say_stuff() |
Direct Assignments And Dictionaries
Python classes seem mutable enough. Maybe you can even directly assign to one method to take the place of the original.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def __init__(self, msg): | |
self.msg = msg | |
def say_stuff(self): | |
print("Standard say stuff message: {}\n\n".format(self.msg)) | |
def say_other_stuff(self): | |
print("New say_stuff says this: {}\n\n".format(self.msg)) | |
MyClass.say_stuff = say_other_stuff | |
ob = MyClass("This is my message here") | |
ob.say_stuff() |
Awesome! That opens up quite a few possibilities right there.
Okay, one last thought. Python classes and instances have these things called dictionaries that dwell beneath the surface. Dictionaries represent methods and attributes.
Now, here's an idea. say_stuff isn't a member of the instance dictionary. It's a member of the class that the instance is based on. If there no say_stuff in the instance? No problem. Just look it up in the dictionary of the class instead.
Sooooo, what if we exploited the instance dictionary to subvert that expectation.
Now, here's an idea. say_stuff isn't a member of the instance dictionary. It's a member of the class that the instance is based on. If there no say_stuff in the instance? No problem. Just look it up in the dictionary of the class instead.
Sooooo, what if we exploited the instance dictionary to subvert that expectation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(object): | |
def __init__(self, msg): | |
self.msg = msg | |
def say_stuff(self): | |
print("Standard say stuff message: {}\n\n".format(self.msg)) | |
ob = MyClass("Here is the original message") | |
def say_stuff(): | |
m = "Thought I'd say something else, didn't you?" | |
print(m) | |
#setattr(ob, "say_stuff", say_stuff) | |
ob.__dict__["say_stuff"] = say_stuff | |
ob.say_stuff() |
And yes, that setattr trick that's commented out there works too. And there are other things to say and do with these underlying dictionaries too. However, exploring much further requires diving into the mysterious world of metaclasses. Let's not go there.... unless you really want to. ;-)
Well That Was Fun!
This is definitely not something you'd likely want to do with any REAL project unless you absolutely had to. Overuse of monkey patching can make things messy and confusing. At any rate, I hope you enjoyed this read. Have a great week.