Anton P
Anton P

Reputation: 11

Still don't understand self in Python

so I'm novice to Python and faced this self question. I've researched a lot of info but still don't grasp the concept. I have a simple problem from Zelle book - write a class MSDie for multisided dice (typical dice has 6 sides). Each MSDie object will know:1)how many sides it gets, 2)its current value. So the book proposes the following code:

class MSDie:

    def __init__(self,sides):
        self.sides=sides
        self.value=1

    def roll(self):
        self.value=randrange(1,self.sides+1,1)

    def getValue(self):
        return self.value

And i'm wondering why can't the code be the same only without self?

class MSDie:

    def __init__(sides):
        sides=sides
        value=1

    def roll():
        value=randrange(1,sides+1,1)

    def getValue(self):
        return value

Some people say self is needed to refer to the instance object we've created. But I should say that we use object name which refers to the object we've created. Why can't I create d1=MSDie(12) object with my code? Python will know that d1 is an MSDIe class object and that calss has those declared instance variables and methods. So when I want to setValue of my d1 I just invoke my method (without self) and it works. Please explain me on my example why i'm wrong.

Upvotes: 0

Views: 2866

Answers (5)

David P
David P

Reputation: 46

It's important to understand difference between class and object (class instance).

Class is consisted from method declarations together and template for objects (for example class has 2 methods and 1 integer attribute). It's identified by class name only (MSDie).

Object (class instance) is concrete evaluation of it's class, that is it has concrete values for class attributes. It is identified by class name and evaluation of it's attributes (MSDie(sides=3, value=1))

When Python code is executed, program code (methods and functions, so "classes") are loaded into code memory. During program execution, objects are created by executed code and stored in another part of memory - data memory. There are stored values of each attribute together with identification of class for class instance, but nothing from class code (methods) is copied there. Instance can be identified by address (pointer) to data memory box where are stored it's data. It's good to mention, that data and code memory parts are strictly separated. Note there may be thousands instances of same class (so thousands instance boxes in data memory), but code for that class is present only once - in code memory.

When instance method is called in Python:

d1.setValue(3)

Name of instance (d1) is converted to pointer into d1's data memory box. But that box doesn't contain code for method MSDie.setValue(x). There is only identification of class, which can be used to find code for that method in code memory. But when method code is called from code memory, it has no idea what instance it is supposed to work with. This information needs to be passed somehow there.

Most languages with classes handles this passing of instance to class method during code compilation into byte code and it's not visible in language syntax at all. It just may be "confusing" where this (Java analogy to Python's self) comes from as it's not defined anywhere.

Python kept this instance parameter in class methods as part of it's syntax. So it's really not big deal, it works same way in other object oriented languages, they just hide it behind compiler. On the other side, it may be a bit confusing, that class method is defined with 2 parameters (MSDie.setValue(self, x)), but when it's called, only 1 parameter is passed (d1.setValue(3)).

So to answer your question:

When class method is defined like this:

def MSDie:
    ...
    def roll(self):
        self.value=randrange(1,self.sides+1,1)

You need to call it this way (without passing value for self)

d1.roll()

Python will find what class instance is saved in d1 variable and then it calls it's method like this

MSDie.roll(d1)

But this conversion is done on background. And that is how d1 instance is available as self inside of method roll().

You can't omit self parameter in definition of class method simply because Python will not be able to find method with correct number of parameters or values for parameters inside of method will be wrong (1st parameter in method would get 2nd parameter from call, etc). Additionally code inside of method will not get correct instance to work with.

Also you need to use self inside of class method to access all object attributes properly (it's needed to find memory box where these values are stored).

Upvotes: 0

Waylan
Waylan

Reputation: 42497

The short answer is because that is how it was designed. Accept it and move on.

However, there is some usefulness to the design: namespaces and readability.

Consider the following:

foo = 'some value'

class Bar():
    def __init__(self):
        self.foo = foo

Notice that there are two objects named foo. One is globally defined in the module (as foo) and the other is defined at an attribute of the class (as self.foo). If we didn't have self, this wouldn't be possible. Self gives us a different namespace, so that we can differentiate between an object that is assigned to a given class instance, and another one that is not with no concerns about name clashes.

The same applies when working with multiple instances of the same class. For example, when you need to compare two instances (using __eq__ and friends):

class Foo():
    def __eq__(self, other)
        return self.bar == other.bar

Without self, it would not be so clear which bar is which. While not terribly important in an "equal" comparison, in a "greater than" or "less than" comparison, the order matters. And by using self, it makes it clear when you write the code, but also clear when you come back to it months/years later and have to read and understand what it is doing.

Consider a very large class with many attributes which is part of a large module with many objects. Without self, it would not always be clear which objects are class attributes and which are not without reading through the entire module. So, even if the use of self was optional (as you propose) it would still be good practice to always use it to make sure your code is clear when you (or more importantly someone else) comes back to it later. That is just good programming practice. I'm glad the language forces it. In fact, one of Python's strengths is its readability. Self adds to that readability. Embrace it.

Upvotes: 0

chepner
chepner

Reputation: 531165

The way Python implements instance methods, every method receives a reference to the object that invoked the method as its first argument. When you write something like

d1 = MSDie(12)
d1.roll()

Python first checks what the type of d1 is so that it knows what method to call. Seeing that type(d1) is MSDie, it checks to see if MSDie has a method named roll, and finds that it does. It calls that method, passing d1 as the first argument, that is, d1.roll() is equivalent to

MSDie.roll(d1)

Inside of roll(), the reference to d1 is accessed via the self parameter. (The name of the parameter doesn't really matter, but self is the convention that is nearly universally used.)

If you called, instead, d2.roll(), it would be converted to MSDie.roll(d2), and that call would see self as a reference to d2.

If you defined roll as

def roll():
    value=randrange(1,sides+1,1)

Python would have to turn d1.roll() into MSDie.roll(), and the method wouldn't know which dice object to work with. value is simply a local variable to the function, not related to any particular object.

Upvotes: 1

George Cummins
George Cummins

Reputation: 28906

You are a Person; there are many instances of the Person class walking around, but there is only one of you.

If you want to know your eye color, you look at yourself in the mirror. If you want to know how tall you are, you measure yourself. You do not measure Person, because Person is not you.

In the same way, your instance d1 needs to know about itself. MSDie does not have a specific value; d1 must look at its own properties, and those properties are referenced via self.


If someone else wants to measure you, and if you make your personal information available, they don't have to reference self. They can do it like this:

Anton.height

or

Anton.weight

That works okay for very simple properties, but becomes more difficult when the task is more complex. For example, consider the act of sleeping. You have to:

  • lay down
  • close your eyes
  • slow your breathing
  • activate REM
  • activate delta waves

These tasks have a nice wrapper around them, called sleep(). When you tell yourself to sleep(), your body takes over from there:

def sleep(self):
    self.position = 'horizontal'
    self.eyelid = 'closed'
    self.breathing_rate = 30
    self.REM = True
    self.brainwaves.activate('Delta')

If you had to set all of these properties and functions on yourself, you would have had a very rough childhood; you would have had to learn what all of those properties mean before you could use them. Instead, the Person class has the functionality built in, and you just have to call sleep() on your particular instance.

What if the Person class set these properties on the class itself, rather than on self? If that were possible, every instance of Person would lie down, close eyes, slow breathing, etc. at the same time. By referencing self, you can take advantage of the built-in functionality without having to make the details of the functionality part of the public API (ie. something that must be learned externally).

Upvotes: 4

Christoph
Christoph

Reputation: 1557

To a certain extent, this is how Python demands you to write things. Remember: A class is a blueprint, from which objects (i.e. instances of that class) are built.

For example cars (class) have wheels. Hence my car (an actual car, i.e. an object) has wheels. When my car was built, at some point the wheels were attached to my car. The instructions perhaps said "take wheels and put them onto the car currently being constructed". It is conceptually important to note that it would not suffice to just take the wheels from the storage and dump them somewhere in the vicinity of the car. Or onto the blueprint. However, it might say "Grab some screws and put them onto the table for later use". These would then not be delivered with the final car (unless built in at a later stage).

In Python the thing is the same. __init__ describes what to do when an actual object is being built. It needs to distinguish between "make sides a property of my dice" and "store a number in sides so that I can use it later on in the construction but forget about it afterwards".

In contrast to languages, where class interfaces are declared (e.g. C++, Java), Python cannot know whether a dice has sides or not.

In all other cases, one might think it a smart idea if python would actually check whether MSDie has an attribute names sides and use that. However, it might lead to surprising results in more complex classes, when you wish to create a local variable but actually change your object. To prevent such errors, Python demands you to be explicit when addressing attributes.

Upvotes: 0

Related Questions