DBWeinstein
DBWeinstein

Reputation: 9479

Class instantiation and 'self' in python

I know a ton has been written on this subject. I cannot, however, absorb much of it. Perhaps because I'm a complete novice teaching myself without the benefit of any training in computer science. Regardless, maybe if some of you big brains chime in on this specific example, you'll help other beginners like me.

So, I've written the following function which works just fine when I call it (as a module?) as it's own file called 'funky.py':

I type the following into my terminal:

python classy.py

and it runs fine.

def load_deck():
    suite = ('Spades', 'Hearts')
    rank = ('2', '3')
    full_deck = {}
    i = 0
    for s in suite:
        for r in rank:
            full_deck[i] = "%s of %s" % (r, s)
            i += 1
    return full_deck

print load_deck()

When I put the same function in a class, however, I get an error.

Here's my code for 'classy.py':

class GAME():
    def load_deck():
        suite = ('Spades', 'Hearts')
        rank = ('2', '3')
        full_deck = {}
        i = 0
        for s in suite:
            for r in rank:
                full_deck[i] = "%s of %s" % (r, s)
                i += 1
        return full_deck
MyGame = GAME()
print MyGame.load_deck()

I get the following error:

Traceback (most recent call last):
File "classy.py", line 15, in <module>
print MyGame.load_deck()
TypeError: load_deck() takes no arguments (1 given)

So, I changed the definition line to the following and it works fine:

def load_deck(self):

What is it about putting a function in a class that demands the use of 'self'. I understand that 'self' is just a convention. So, why is any argument needed at all? Do functions behave differently when they are called from within a class?

Also, and this is almost more important, why does my class work without the benefit of using init ? What would using init do for my class?

Basically, if someone has the time to explain this to me like i'm a 6 year-old, it would help. Thanks in advance for any help.

Upvotes: 5

Views: 5519

Answers (3)

Jon Jay Obermark
Jon Jay Obermark

Reputation: 365

If you don't intent to use self you should probably declare the method to be a staticmethod.

class Game:
    @staticmethod
    def load_deck():
        ....

This undoes the automatic default packing that ordinarily happens to turn a function in a class scope into a method taking the instance as an argument.

Passing arguments you don't use is disconcerting to others trying to read your code.

Most classes have members. Yours doesn't, so all of its methods should be static. As your project develops, you will probably find data that should be accessible to all of the functions in it, and you will put those in self, and pass it around to all of them.

In this context, where the application itself is your primary object, __init__ is just the function that would initialize all of those shared values.

This is the first step toward an object-oriented style, wherein smaller pieces of data get used as objects themselves. But this is a normal stage in moving from straight scripting to OO programming.

Upvotes: 0

dbr
dbr

Reputation: 169535

So, why is any argument needed at all?

To access attributes on the current instance of the class.

Say you have a class with two methods, load_deck and shuffle. At the end of load_deck you want to shuffle the deck (by calling the shuffle method)

In Python you'd do something like this:

class Game(object):
    def shuffle(self, deck):
        return random.shuffle(deck)

    def load_deck(self):
        # ...

        return self.shuffle(full_deck)

Compare this to the roughly-equivalent C++ code:

class Game {
    shuffle(deck) {
        return random.shuffle(deck);
    }
    load_deck() {
        // ...

        return shuffle(full_deck)
    }
}

On shuffle(full_deck) line, first it looks for a local variable called shuffle - this doesn't exist, to next it checks one level higher, and finds an instance-method called shuffle (if this doesn't exist, it would check for a global variable with the right name)

This is okay, but it's not clear if shuffle refers to some local variable, or the instance method. To address this ambiguity, instance-methods or instance-attributes can also be accessed via this:

...
load_deck() {
    // ...

    return this->shuffle(full_deck)
}

this is almost identical to Python's self, except it's not passed as an argument.

Why is it useful to have self as an argument useful? The FAQ lists several good reasons - these can be summarised by a line in "The Zen of Python":

Explicit is better than implicit.

This is backed up by a post in The History of Python blog,

I decided to give up on the idea of implicit references to instance variables. Languages like C++ let you write this->foo to explicitly reference the instance variable foo (in case there’s a separate local variable foo). Thus, I decided to make such explicit references the only way to reference instance variables. In addition, I decided that rather than making the current object ("this") a special keyword, I would simply make "this" (or its equivalent) the first named argument to a method. Instance variables would just always be referenced as attributes of that argument.

With explicit references, there is no need to have a special syntax for method definitions nor do you have to worry about complicated semantics concerning variable lookup. Instead, one simply defines a function whose first argument corresponds to the instance, which by convention is named "self."

Upvotes: 1

Antimony
Antimony

Reputation: 39451

Defining a function in a class definition invokes some magic that turns it into a method descriptor. When you access foo.method it will automatically create a bound method and pass the object instance as the first parameter. You can avoid this by using the @staticmethod decorator.

__init__ is simply a method called when your class is created to do optional setup. __new__ is what actually creates the object.

Here are some examples

>>> class Foo(object):
    def bar(*args, **kwargs):
        print args, kwargs

>>> foo = Foo()
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x01C9FEB0>>
>>> Foo.bar
<unbound method Foo.bar>
>>> foo.bar()
(<__main__.Foo object at 0x01C9FEB0>,) {}
>>> Foo.bar()

Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    Foo.bar()
TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)
>>> Foo.bar(foo)
(<__main__.Foo object at 0x01C9FEB0>,) {}

Upvotes: 4

Related Questions