loading...

Why self is not this in Python!

hanpari profile image Pavel Morava Updated on ・3 min read

One of the most annoying objections towards Python, coming usually from programmers who knows better any other language, aims at the "verbose" signature of methods in Python, especially this:

class Self:
    name = "Python's self"
    def __repr__(self):
        return self.name

In general, they think Python's self is crippled and wrongly implemented this from C#, Java or whatever they fancy. Hm, that's not the case al all. The word self you say? What self? Never heard of it!

class Me:
    name = "Visual Basic's me"
    def __repr__(me):
        return me.name

class This:
    name =  "Java's this"
    def __repr__(this):
        return this.name

Self(), Me(), This()
(Python's self, Visual Basic's me, Java's this)

As you can see for yourself, self is just a name, not a keyword, and as such absolutely mandatory.

What does it mean for you? For instance, consider:

# Two different way to get the same result.
"hello".upper() == str.upper("hello")
True
# Partial application in practice, with no partial function necessary.
join_with_dot = ".".join
join_with_dot(("one", "two", "three"))
'one.two.three'

Did I mention C# here? Let's look at its extension methods. To demonstrate how it works I was forced to create this mud-dwelling monstrosity. If I am not mistaken, I called Csharp in its latest iteration a decent language. I was lying...


using System;

namespace csplay
{

    public static class Extension
    {
        public static string Hello(this string name) => $"Hello, {name}!";
    }


    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("World".Hello());
        }
    }
}

Now let's focus on this line:

public static string Hello(this string name) => $"Hello, {name}!";

Notice the this keyword here. The concept is the same, the level of verbosity enormous.

Custom method

And now for something completely different.

Do you think you know how to write a method in Python?

What about this, then?


def fullname(person):
    return f"{person.surname}, {person.name}."

class Scientist:

    def __init__(self, name, surname):
        self.name = name
        self.surname = surname

    get_fullname = fullname

    # I cannot use @property decorator but I can do this to make property
    fullname = property(fullname)



scientist = Scientist("Jaroslav", "Heyrovský")
scientist.fullname, scientist.get_fullname()
('Heyrovský, Jaroslav.', 'Heyrovský, Jaroslav.')

Method for events

class State:
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self.on_change_value(old = self._value, new = value)
        self._value = value

    def on_change_value(*args, **kwargs):
        pass


def print_change(old, new):
    print(f"{old} changed to {new}.")

state = State(10)

# nothing happens
state.value = 9

# now we going to process event
state.on_change_value = print_change

state.value = 11
state.value = 12

9 changed to 11.
11 changed to 12.

The final riddle

Note that print_change is missing the self parameter. It is because we are assigning this function to instance body, not class definition as we did with fullname in the previous example.

To fully understand what is happening here, suffice it to say that Python's methods are just partial application of functions bound to classes which are, actually, just disguised dictionaries in most cases.

So once again, shown from a completely different angle, step by step. No fancy class necessary!

from functools import partial

# Some function we want to use.
# See, self is completely arbitrary here.
def get_name(self):
    return self.name

# Create type person in strange and mysterious way
# No class necessary? Is it black magic?
Person = type("Person", (), {"name": "unknown hero"})

# Creating instance of class Person
ilya = Person()

# But the underlying dictionairy has no idea what Person we mean.
# I could use prepare __init__ method in advance but seriously
# I did not intend to confuse you more than necessary.
print("So far it is: ", ilya.name)

# So I assign a new name to the instance of Person.
ilya.name = "Ilya Muromec"

# Now partially applying  our instance to self in get_name function.
ilya.get_name = partial(get_name, self=ilya)

# Now we verify it works as expected.
# Hm, perhaps, I should say in the way I expected.
print("Now it is: ", ilya.get_name())
So far it is:  unknown hero
Now it is:  Ilya Muromec

If you were able to follow me until now, you may wonder what is partial, partial application, the unusual signature of type builtin, or what I did at all. Perhaps you are wondering who were Jaroslav Heyrovský or Ilya Muromets?

I leave the rest for self-studying.

Posted on by:

hanpari profile

Pavel Morava

@hanpari

Majored in environmental technologies. Plastic processing engineer for over 15 years. Programming for over 30 years. Author of Pavel Morava's Sovereign.

Discussion

markdown guide
 

In Next Generation Shell I went for much simpler model which (having it's own downsides) escapes the horrors above.

NGS has types, methods and multiple-dispatch but no classes. Want to add your own method to built-in Str type? No problem. F hello(s:Str) "Hello, ${s}". Done. Syntactically looks exactly as if you were defining a method for your own type.

Don't know whether method sit_on_chair() belongs to Person or Chair class? No problem because F sit_on_chair(p:Person, c:Chair) ... does not "belong" to any class or type. It's just a method.

NGS was heavily inspired by CLOS but is simpler.

Partial application? NGS has syntax for that. join_with_dot = join(X, ".") and since uniform function call syntax is implemented one can also define join_with_dot as join_with_dot = X.join("."). The call would then be join_with_dot(["one", "two", "three"]) or ["one", "two", "three"].join_with_dot()

 

In general, I believe we think alike. If I am not mistaken, you have just describe something I coincidentally wrote about in one of my previous article.

dev.to/hanpari/how-to-use-single-d...

Avoiding classes, in the sense of mixing functions and states together, is only praiseworthy. Unfortunatelly, Python is a relict from times when classes were in and considered a silver bullet.

I would agree with everything, except for the multiply dispatch, which in my opinion is not a good idea, especially when it comes to refactoring. Single dispatch where the first argument may be represented by a discriminated union of supported datatypes, exactly in the way described in the wikipedia's article about Uniform Call Syntax (btw, thank you for the link) should suffice.

If one needs more arguments, something like ad hoc named tuple is better alternative.

 

multiply dispatch, which in my opinion is not a good idea, especially when it comes to refactoring

Can you please elaborate?

Sure, let me demonstrate:

Multiple dispatch

function (x:Person, y:Animal)
function (x:Person, y:Person)
function (x:Person, y:Person)
function (x:Animal, y:Animal)

Discriminated unions approach

function (x: Person | Animal, y: Person | Animal)

Those two are equivalent. The problem with the multiple dispatch occurs as soon as you try add one or n-more more parameters.

Adding an integer to DU function

function (x: Person | Animal, y: Person | Animal, z: Integer=0)

As you can see, I was able to add the parameter at one position, keeping the DRY principle.

Adding an integer to DU function

function (x:Person, y:Animal, z: Integer = 0)
function (x:Person, y:Person, z: Integer = 0)
function (x:Person, y:Person, z: Integer = 0)
function (x:Animal, y:Animal, z: Integer = 0)

This is a pure WET (We Enjoy Typing) principle in practice, at least in my opinion. And things get worse as soon as you try add more and more parameters.

This is going to be messy and utterly unsafe. Single dispatch limits this tendency and prevents you from possible troubles in the future.

To make simple dispatch less hairy you can leverage tuples to imitate multiple dispatch:

type LivingCreatures = (Animal, Person) | (Person | Animal) | ...

The function signature results in:

function (x: LivingCreatures, z: Integer=0)

To put it simply, I believe the multiple dispatch compromises a language safety and conciseness. But I may be wrong. :)

I never experienced this kind of pain and I'm using Next Generation Shell (with multiple dispatch) for years now.

function (x:Person, y:Animal, z: Integer = 0)
function (x:Person, y:Person, z: Integer = 0)
function (x:Person, y:Person, z: Integer = 0)
function (x:Animal, y:Animal, z: Integer = 0)

Any function that does use the z parameter - I don't see neither a problem with adding parameter nor a way around that. (Am I missing something here?).

Any function that does not use the z parameter - there is no need to add it, at least in NGS.

If NGS allows default arguments, then it may be OK. Julia, as far as I know, uses multiple dispatch and its users are happy. Recently, I run into a nasty problem with an overloaded method in C#, where I had to supply an argument I did not care for because the overloading did not cover my use case.

Still, I am not convinced that multiple dispatch is a good design for a modern language. But I may be wrong, of course. The language I have on my mind differs heavily from everything I've seen so far.

I took the example from wiki and thinking about it, even the example itself is crippled and code smell on its own. Actually, instead of using signature with types, much simpler and more correct would be just to rely on interfaces or traits.

So instead

function(x: Animal, y:Person, z: integer)
...

Would be just one

function(x: CommonTraitForAnimalAndPerson, y: CommonTraitForAnimalAndPerson, z: integer)

This one is actually more concise and general than any previous solution.

 

Wow, thanks for solving this riddle. I really never knew this!

 

Thanks for the kind comment. I was wondering to what extent was my explanation approachable.