Doing Python OOP the wrong way
In the name of science!If you know me, you probably know of the many weird things I do with python. Most recent of which being this FizzBuzz implementation in one line of python code:
_=[print("FizzBuzz"[_*_%3*4:8--_**4%5] or _) for _ in range(101)]
This installment of “weird things I do with python” will not focus on one-liners (that’s going on my todo list though). But instead, playing with Python’s classes and object system.
A quick introduction to classes
Im going to assume that you, the reader, have some reasonable knowledge of how computers work, and OOP concepts. If you do not, there are many great online resources to help you out.
As a quick refresher, this is the Python syntax for a basic class:
class MyClass:
# This is the constructor. __init__ is an overridable python built-in
def __init__(self, arg1: int):
# Here we set the class' scoped my_number to arg1
self.my_number = arg1
def printMyNumber(self):
print(self.my_number)
This is really just a fancy setter and getter. Here is some example usage:
my_object = MyClass(10)
my_object.printMyNumber() # Prints 10
Noticing something odd
Before reading the following, keep in mind that (as of now) I have not actually looked at the Python interpreter’s source code enough to know about their memory system. The following is just an educated guess.
Looking at any python class, you may notice that at least 1 argument is required. self
is used to access the class’ data from itself. This is not present in most other languages I know, which means there might be something interesting happening behind the scenes. Here is a re-implementation of MyClass
from above in java:
public class MyClass {
int my_int;
public MyClass(int arg1){
my_int = arg1;
}
public void printMyNumber(){
System.out.println(my_int);
}
}
Notice the fact that there is no self
? Yet Java methods can still access class data.
Implementing objects in a non-object oriented language
In a non-OOP language (like C), objects can be faked by creating structures and some standard functions. These functions then take a pointer to their “parent” structure. Confusing? yes. But it works, and I see it used all over the place. Here a pseudocode example:
struct MyClass {
int my_int; // Scpoed int
}
fn printMyNumber(MyClass* self){
print(self.my_int);
}
printMyNumber
takes a pointer to it’s “parent class”, called self
. Look familiar? This is how Python works.
Let’s do some Python
Alright.. Time for some “broken” Python. Here is yet another implementation of MyClass
, except this time, each function is globally scoped:
# Private, globally scoped functions
def _init_myclass(self, arg1: int):
self.my_number = arg1
def _myclass_printMyNumber(self):
print(self.my_number)
# struct-like class containing function pointers
class MyClass:
__init__ = _init_myclass
printMyNumber = _myclass_printMyNumber
This code will still function like a normal class. Unlike a regular class definition, the above code defines the constructor and printMyNumber
methods in the global scope (marked as private with an underscore). A class is then created with function pointers to each of the global functions. This means that calling MyClass.printMyNumber
will point to, and execute _myclass_printMyNumber
. The interpreter still treats the underscore functions as members of MyClass
, and passes the self
argument along to them.
Why?
I have absolutely no idea why this would ever be useful. If you think you should start doing this in your code, don’t. It leads to very messy and confusing code, and is bad practice in just about every way.
The point of this post is to show yet another instance of the Python interpreter saying “idgaf”, and letting us have a little fun.