Reputation: 75
i am starting my studies on OPP and I am struggling a little to grasp one concept. I believe this is very basic for you guys here, but I was hoping to get some help with it.
I have this code:
class Player:
MAX_POSITION = 10
def __init__(self):
self.position = 0
# Add a move() method with steps parameter
def move(self, steps):
if self.position + steps < Player.MAX_POSITION:
self.position = self.position + steps
else:
self.position = Player.MAX_POSITION
It is correct, it runs... but I am struggling to learn why I need the prefix self
on the position
argument and not on the steps
within the function move
for the class Player
?
any explanations or reading material suggestions are more than welcome thanks
Upvotes: 1
Views: 2120
Reputation: 41
Recently I faced quite similar problem and when searching the answer I stumbled your question and comprehensive answer given above by Grismar. I just want to bring here some practical examples that will show a bit more and reveal under-the-hood gears.
Let's start with your code. If we call a few instances like below this will bring us a correct result.
p1 = Player()
p1.move(100)
print('p1.__dict__ ->', p1.__dict__)
p2 = Player()
p2.move(1)
print('p2.__dict__ ->', p2.__dict__)
# output
# p1.__dict__ -> {'position': 10}
# p2.__dict__ -> {'position': 1}
Notations p1.__dict__
and p2.__dict__
show what values are actually assigned to a position
variable.
So, what if we would like to use variable steps
somewhere in another method (or methods)? I modified your code just a bit by adding additional method jump()
to demonstrate what will happen.
This code will not work correctly for the method jump()
. But the method move()
still correctly executed.
class Player:
MAX_POSITION = 10
def __init__(self):
self.position = 0
# Add a move() method with steps parameter
def move(self, steps):
if self.position + steps < Player.MAX_POSITION:
self.position = self.position + steps
else:
self.position = Player.MAX_POSITION
def jump(self):
print(steps * 2)
p1 = Player()
p1.move(100)
print('p1.__dict__ ->', p1.__dict__)
p1.jump()
# output
# p1.__dict__ -> {'position': 10}
# ---------------------------------------------------------------------------
# NameError: name 'steps' is not defined
To fix the problem and remove exception (here it is NameError: name 'steps' is not defined
) we have to «glue» self
and steps
.
class Player:
MAX_POSITION = 10
def __init__(self):
self.position = 0
# Add a move() method with steps parameter
def move(self, steps):
self.steps = steps # added line
if self.position + steps < Player.MAX_POSITION:
self.position = self.position + steps
else:
self.position = Player.MAX_POSITION
def jump(self):
print('jump() method ->', self.steps * 2) # added self
p1 = Player()
p1.move(100)
print('p1.__dict__ ->', p1.__dict__)
p1.jump()
# output
# p1.__dict__ -> {'position': 10, 'steps': 100}
# jump() method -> 200
Now we can see that the method move()
executed correctly (variable position
assigned to 10
). The method jump()
executed without any exceptions as well. Instance p1
has now two available variables, they are 'position': 10
and 'steps': 100
.
In other words self.variable
notation gives the option to use the variable beyond the «parent» method.
Upvotes: -1
Reputation: 31461
In an example like this:
def class MyClass:
def __init__():
self.attribute = None
def my_method(self, some_parameter):
self.attribute = some_parameter
attribute
is defined as an attribute (or instance variable) on MyClass
. This means that every instance of MyClass
(a MyClass
object) will have its own copy of attribute
.
Since every normal method (a function defined on the class) expects the first argument to be the object itself (which you don't have to pass; Python will pass it automatically), you can use self
to refer to the instance the method was called on. (I say 'normal', because there are also 'static' and 'class' methods, but forget about those for now.)
So, in this example:
an_object = MyClass()
an_object.my_method(10)
print(an_object.attribute)
This works, because in the body of .my_method
, the passed value, which gets assigned to some_parameter
is assigned to the attribute
attribute of an_object
, because an_object
is assigned to self
in that call. Mind you, self
could have been called anything; naming the first parameter self
is just a convention you should follow.
The reason some_parameter
does not need self.
is because it is just another parameter of a function. It's not an attribute of self
— of the object the method was called on.
So, when compared to your code, you should say: steps
does not need self.
because it is not an attribute of a Player
instance. It is just a parameter of a method defined on Player
, and the value is accessible like any parameter is in a function body. The instance of a Player
object is passed as self
, and you can change its attributes by accessing them on self
inside the function body.
A clue why you didn't grasp this is that you call position
an 'argument', but an argument is something passed to a function, to a specific parameter; and a parameter is an internal variable of a function that is assigned the argument. position
is an attribute of an object.
So, when calling player.move(2)
, 2
is the argument, steps
is the parameter in the body of move()
and position
is the attribute of player
, with player
being the Player
class instance (or 'object') accessible through the self
parameter in the body of move()
.
Upvotes: 4