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

  1. Functions in Javascript are first class
    We can dynamically change the methods and functions that a class has, hence changing what this refers to depending on where it was called.

  2. 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
    16
    function 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:

  1. 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.
  2. this is undefined outside of method contexts
    In Java, everything is an object, hence this is always defined. In C++, it simply errors with invalid 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
2
3
4
5
6
7
8
9
10
class Value:
def __init__(val):
this.val = val

def display():
print(this.val)

my_value = Value("this :/")
my_value.display()
# this :/

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
2
3
4
5
6
7
8
9
10
class Value:
def __init__(self, val):
self.val = val

def display(self):
print(self.val)

my_value = Value("self :pogu:")
Value.display(my_value)
# self :pogu:

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
2
3
4
5
6
7
8
9
10
11
class Value:
def __init__(self, val):
self.val = val

def display(self):
print(self.val)

my_value = Value("self :pogu:")
my_value.display()
# becomes Value.display(my_value)
# self :pogu:

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
2
3
4
5
6
7
8
9
10
class Value:
def __init__(thingamajig, val):
thingamajig.val = val

def display(thingamajig):
print(thingamajig.val)

my_value = Value("thingamajig")
my_value.display()
# thingamajig

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
2
3
4
5
6
7
8
def to_upper(string):
return string.upper()

for caps in map(to_upper, ["hello", "wOrlD"]):
print(caps)

# HELLO
# WORLD

But since string.upper() is the same as str.upper(string), we’ve actually just done

1
2
def to_upper(string):
return str.upper(string)

Which is redundant. Let’s just pass in str.upper!

1
2
3
4
5
for caps in map(str.upper, ["hello", "wOrlD"]):
print(caps)

# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Value:
def __init__(self, val):
self.val = val

def display2():
print("property invoked!")

self.display = display2

def display(self):
print("method invoked", self.val)

my_value = Value("hmmm")
my_value.display()
# property invoked!

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
2
Value.display(my_value)
# method invoked hmmm

Note that the shorthand wasn’t applied on display2, else we would have too many arguments for the function.

graph TD; a("instance.method()") --> b{{"instance has property method?"}} b -- Yes --> c("method()") b -- No --> d{{"Class has property method?"}} d -- Yes --> e("Class.method(instance)") d -- No --> f("AttributeError") style c stroke-width:2px,fill:none,stroke:green; style e stroke-width:2px,fill:none,stroke:green; style f stroke-width:2px,fill:none,stroke:red;

In this context, Class having method would include parent classes having method.