Python: must __init__(self, foo) always be followed by self.foo = foo?
Asked Answered
P

3

5

I've been striving mightily for three days to wrap my head around __init__ and "self", starting at Learn Python the Hard Way exercise 42, and moving on to read parts of the Python documentation, Alan Gauld's chapter on Object-Oriented Programming, Stack threads like this one on "self", and this one, and frankly, I'm getting ready to hit myself in the face with a brick until I pass out.

That being said, I've noticed a really common convention in initial __init__ definitions, which is to follow up with (self, foo) and then immediately declare, within that definition, that self.foo = foo.

From LPTHW, ex42:

class Game(object): 
    def __init__(self, start): 
        self.quips = ["a list", "of phrases", "here"]
        self.start = start

From Alan Gauld:

def __init__(self,val): self.val = val

I'm in that horrible space where I can see that there's just One Big Thing I'm not getting, and I it's remaining opaque no matter how much I read about it and try to figure it out. Maybe if somebody can explain this little bit of consistency to me, the light will turn on. Is this because we need to say that "foo," the variable, will always be equal to the (foo) parameter, which is itself contained in the "self" parameter that's automatically assigned to the def it's attached to?

Pretonic answered 16/3, 2012 at 0:48 Comment(0)
C
7

You might want to study up on object-oriented programming.

Loosely speaking, when you say

class Game(object):
    def __init__(self, start):
        self.start = start

you're saying:

  • I have a type of "thing" named Game

  • Whenever a new Game is created, it will demand me for some extra piece of information, start. (This is because the Game's initializer, named __init__, asks for this information.)

  • The initializer (also referred to as the "constructor", although that's a slight misnomer) needs to know which object (which was created just a moment ago) it's initializing. That's the first parameter -- which is usually called self by convention (but which you could call anything else...).

  • The game probably needs to remember what the start I gave it was. So it stores this information "inside" itself, by creating an instance variable also named start (nothing special, it's just whatever name you want), and assigning the value of the start parameter to the start variable.

    • If it doesn't store the value of the parameter, it won't have that informatoin available for later use.

Hope this explains what's happening.

Christychristye answered 16/3, 2012 at 0:56 Comment(4)
Important to mention is though that you can assign the argument to any attribute, like self.abc. Naming the attribute the same way is just a matter of how you structure your code.Grits
@FelixKling: I made a slight edit to make that a bit more clear, thanks.Christychristye
I think I'm almost there, but could use a little help on your third point: are you saying that the third line of your code block could be "self.anything = start", or "self.start = anything"? Clearly one should be consistent with the init parameter in parentheses, but I'm not sure which is the instance variable and which is the parameter in that line. Apologies for being a thickwit.Pretonic
@mattshepherd: The instance variable could be anything (self.anything), but the parameter obviously has to match (start).Christychristye
L
2

I'm not quite sure what you're missing, so let me hit some basic items.

There are two "special" intialization names in a Python class object, one that is relatively rare for users to worry about, called __new__, and one that is much more usual, called __init__.

When you invoke a class-object constructor, e.g. (based on your example) x = Game(args), this first calls Game.__new__ to obtain memory in which to hold the object, and then Game.__init__ to fill in that memory. Most of the time, you can allow the underlying object.__new__ to allocate the memory, and you just need to fill it in. (You can use your own allocator for special weird rare cases like objects that never change and may share identities, the way ordinary integers do for instance. It's also for "metaclasses" that do weird stuff. But that's all a topic for much later.)

Your Game.__init__ function is called with "all the arguments to the constructor" plus one stashed in the front, which is the memory allocated for that object itself. (For "ordinary" objects that's mostly a dictionary of "attributes", plus the magic glue for classes, but for objects with __slots__ the attributes dictionary is omitted.) Naming that first argument self is just a convention—but don't violate it, people will hate you if you do. :-)

There's nothing that requires you to save all the arguments to the constructor. You can set any or all instance attributes you like:

class Weird(object):
    def __init__(self, required_arg1, required_arg2, optional_arg3 = 'spam'):
        self.irrelevant = False
    def __str__(self):
        ...

The thing is that a Weird() instance is pretty useless after initialization, because you're required to pass two arguments that are simply thrown away, and given a third optional argument that is also thrown away:

x = Weird(42, 0.0, 'maybe')

The only point in requiring those thrown-away arguments is for future expansion, as it were (you might have these unused fields during early development). So if you're not immediately using and/or saving arguments to __init__, something is definitely weird in Weird.

Incidentally, the only reason for using (object) in the class definition is to indicate to Python 2.x that this is a "new-style" class (as distinguished from very-old-Python "instance only" classes). But it's generally best to use it—it makes what I said above about object.__new__ true, for instance :-) —until Python 3, where the old-style stuff is gone entirely.

Lingua answered 16/3, 2012 at 1:10 Comment(0)
G
0

Parameter names should be meaningful, to convey the role they play in the function/method or some information about their content.

You can see parameters of constructors to be even more important because they are often required for the working of the new instance and contain information which is needed in other methods of the class as well.

Imagine you have a Game class which accepts a playerList.

class Game:

    def __init__(self, playerList):
        self.playerList = playerList    # or self.players = playerList

    def printPlayerList(self):
        print self.playerList           # or print self.players

This list is needed in various methods of the class. Hence it makes sense to assign it to self.playerList. You could also assign it to self.players, whatever you feel more comfortable with and you think is understandable. But if you don't assign it to self.<somename> it won't be accessible in other methods.

So there is nothing special about how to name parameters/attributes/etc (there are some special class methods though), but using meaningful names makes the code easier to understand. Or would you understand the meaning of the above class if you had:

class G:

    def __init__(self, x):
        self.y = x

    def ppl(self):
        print self.y

? :) It does exactly the same but is harder to understand...

Grits answered 16/3, 2012 at 1:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.