The elegance of self
this sucks
The trouble with this
Unlike many (JS, Java, CPP), OOP supporting languages, Python doesn’t have this
. Had you tried to import this
, you’d be left with a neat easter egg. I won’t go into detail with specific reasons I dislike this
in this post, rather I’ll leave it for another post. In general though, the issues I face are related to implicitness.
Implicit this
In Javascript, the keyword this
is determined based on several things, namely how the function was defined, where the function was called, and whether or not it was bound. That’s a few too many cases for me. These cases and issues come about because
Functions in Javascript are first class
We can dynamically change the methods and functions that a class has, hence changing whatthis
refers to depending on where it was called.Javascript wants
this
to be defined even outside of a method scope (non-strict mode)this
is usually used in OOP contexts, but Javascript wants it to be defined even outside of methods and constructors. This leads to interesting bugs, such as when an improperly called constructor causes variables to be set in the global scope.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function Value(val) {
this.val = val;
}
Value.prototype.display = function() {
console.log(this.val);
}
// correct
v = new Value("constructor!");
v.display(); // constructor!
// incorrect
v = Value("constructor?");
v.display() // Uncaught TypeError: Cannot read property 'display' of undefined
console.log(val) // "constructor?"
These issues don’t show themselves in a language like Java and C++, because:
- Their functions aren’t first class
It’s impossible to use the method defined for class A as if it was defined in class B. this
is undefined outside of method contexts
In Java, everything is an object, hencethis
is always defined. In C++, it simply errors withinvalid use of ‘this’
Python is closer to Javascript in terms of what’s allowed, so we better figure something out.
The elegance of self
Explicit self
So, we’ve looked at some reasons I dislike this
. What’s the alternative? Why, self
, of course! To quote the Zen of Python,
Explicit is better than implicit
Let’s first imagine a python-this hybrid.
1 | class Value: |
Now, since we don’t want a keyword this
, how else can we get the instance? Well, why not make it an argument? This should reduce the opacity of what’s going on considerably. self
will explicitly refer to the instance that is calling the method, and will hence need to be passed in as an argument.
1 | class Value: |
Implicit self
Unfortunately, we’ve lost the neat notation instance.method()
. But while we’re dreaming of languages like the python-this hybrid, why don’t we just add a shorthand? Let’s just make instance.method()
be shorthand for Class.method(instance)
. We’ll keep the neatness, while retaining the transparency of self within the function definition. Excellent!
1 | class Value: |
As a matter of fact, this is how python handles instance methods! This idea is borrowed from Modula-3, and is much more elegant. We define a function that explicitly takes in a self
argument: Class.method(instance)
, then (optionally) implicitly pass it in through instance.method()
syntax.
We have an explicit self that is referenced in the function parameter, leaving little ambiguity. We can always trace back what self
refers to, without needing to juggle the different declarations that Javascript has. self
is nothing more than an argument, a method is no more than a function that happens to take in an self
. Nothing special.
Implications
self isn’t special
Well now you might be thinking, “Does this mean that self
isn’t special”? You’d be partially correct. The first argument of a class method will be filled with instance when the shorthand is used. The name self
? Nothing special. self
is more of a useful convention. For example,
1 | class Value: |
runs perfectly fine in standard python. However, this doesn’t mean that you should change self
to thingamajig
everywhere. While self
is “just” a convention, it is still a convention. Editors, code highlighting software and intellisense may not understand thingamajig
, not to mention what happens if a piece of code passes self
as a keyword argument. Keep it to self
.
Methods as functions
Here’s something to think about. Functions in python are first order, and methods are just functions of a class. So if we need a function that just calls a method, we might do something like:
1 | def to_upper(string): |
But since string.upper()
is the same as str.upper(string)
, we’ve actually just done
1 | def to_upper(string): |
Which is redundant. Let’s just pass in str.upper
!
1 | for caps in map(str.upper, ["hello", "wOrlD"]): |
Property priority
If my_value.method()
is converted to Value.method(my_value)
, what happens if the instance my_value
has a property method
? Let’s find out:
1 | class Value: |
As it turns out, the property has priority. Note that display2
has no self
parameter since it will not be expanded into display2(my_value)
. my_value.display()
really is just a call to display2()
.
The shadowing of the method shouldn’t be too big of a deal. If your code has a method and property named the same thing, that’s on you. However, as we learnt earlier, we can simply call the method directly from the parent class if we need it.
1 | Value.display(my_value) |
Note that the shorthand wasn’t applied on display2
, else we would have too many arguments for the function.
In this context, Class
having method
would include parent classes having method
.