Reputation: 79
I am a newbie to Python and programming in general and I have just gotten into OOP. In Python, whatever is defined in a function's namespace is only valid while inside that namespace; once outside that space things like variables are forgotten:
def foo():
a = 3
print(a)
NameError: name 'a' is not defined
As far as I know, besides returning data from functions, any information inside the function is lost at the end of the function call, and therein lies my question. Take the following code:
class Example:
def __init__(self, name):
self.name = name
def foo(self):
self.name = 'John'
bar = Example('Jake')
bar.foo()
print(bar.name)
'John'
My question is: why don't you have to return objects after methods? In normal functions any variables are forgotten, but in methods it seems data is actually appended to the object itself, such as the foo()
method being able to reference self.name
despite self.name
first being referenced in another method. Is this correct? Or is there a better technical explanation?
Upvotes: 6
Views: 5510
Reputation: 1097
Well, this is what is called as 'scoping' in OOP. And though the difference between classes and functions might seem subtle, it makes a big difference.
Simply said, functions rely on global and local variables. Global variables are objects that you define in the global workspace. This is like having a workbench and getting to use a common object across your functions and classes that you define. And so, for example, if you modify your function foo()
to include an argument, like so:
a = 3 # This is globally defined
def foo(a):
a = 3 * a
return a
print(foo(a))
9
Changing the global value of a
to something like 2
will give you 6 as the answer. We 'return' an object because it is locally defined within the scope of a function. In this example above, foo()
performs the local operation 3 * a
and defines it locally to a
itself and returns it. And so you can also skip the global variable a
when executing foo()
and define it locally within the function scope:
print(foo(6)) # Locally defined
18
print(a)
3 # Returns global variable
Classes on the other hand need to be declared and that has been very nicely explained by members (see above).
Upvotes: 1
Reputation: 2209
Welcome to Stack Overflow. You are thinking in the right direction. I'll start at a more basic level so that other beginner viewers can understand what is going on.
Think of Example
as a template for creating new objects of a certain kind -- meaning the objects will all have the same attributes (also known as properties) and functionalities. For example, by drawing an analogy with real-life objects, all cars have the attribute "speed" and the functionality "accelerate".
The attribute values will be specific to the objects. For example, one car will have 0 mph speed and another 70 mph.
The attribute values at any point describe the state of an object. Methods, which you can think of as the object's functionalities, allow the ability to change an object's state.
bar
is an object that you created using the template (that is, class
) Example
. If you have to describe this object's state, you'd tell us the values of its attributes. In this case, the object has only one attribute: name
.
Now, this is the important part: name
is not just any variable, it's an attribute of any object you make from the class Example
. Think of name
as always attached to the object. That's why you wrote self.name = 'John'
and not name = 'John'
. In the latter case, name
would not be a part of the object bar
, and no other method in the class would have access to the name
variable.
So, to summarize, when you have created an object out of a class, think of the object as having various attributes. All the methods, or functionalities, of the object will have access to all those attributes. The values of the attributes would describe, at any point, the object's state at that moment. It is through that object's methods that one would change its state.
Finally, here's a great tool to visualize what happens at each line of your code: pythontutor.com
Upvotes: 2
Reputation: 845
There isn't a lot of difference between methods and functions (See this thread for details)
Though one important distinction, that you might have immediately noticed, is that methods have self as their first argument and we say that method foo is "bound" to instance bar of class Example, meaning simply that foo will be called with its first argument (self) as the instance (bar) itself
With this knowledge lets see what the following function does:
class Example:
def __init__(self, name):
self.name = name
def foo(self):
self.name = 'John'
bar = Example('Jake')
In method init, you assign name to self. But what is self? It is bar itself, so calling init can be thought of as doing
bar.name = 'Jake'
Then when you called method foo
bar.foo()
You equivalently did
bar.name = 'John'
So, it should not be surprising when finally the output of the following was 'John'
print(bar.name) # John
About your query regarding methods not having to return anything, it is not quite so. Methods and functions may or may not return anything as (See this answer). But in this context, what is happening is an object passed to a method is being manipulated (self, which is bar, is being assigned a name) and because the object is alive after the method call completes, we can observe the changes done by the method (ie, we can see that bar.name is changed to 'John')
This works works with functions as well:
def another_foo(self):
self.name = 'Mark'
baz = Example('Jake')
another_foo(baz)
print(baz.name) # Mark
You see, this function did not return anything either. It worked just like the method foo by manipulating its argument. In fact, you could add it to the Example class and use it like a method
Example.another_foo = another_foo
new_bar = Example('Jake')
print(new_bar.name) # Jake
new_bar.another_foo()
print(new_bar.name) # Mark
Upvotes: 3
Reputation: 369
To understand this you will need to understand how self works. You can learn more here: Understanding self in python
In a nutshell, self refers to the calling object. Invoking self.variable
refers to the variable associated with the calling object. Python is smart enough to create one if it doesn't exist.
Calling self.variable
inside a class is the same as calling object.variable
with your object reference
Consider the following example to prove this:
class Example:
def print_x(self):
print(self.x)
obj = Example()
obj.x = 5; # Create a new attribute of the object and assign it a value 5
print(obj.x) # Outputs 5
obj.print_x() # Outputs 5
In your example, I've added a couple of print statements to help you understand the state of the program during the execution:
class Example:
def __init__(self, name):
print(dir(self)) # Printing object contents before initializing name
self.name = name # New attribute 'name' created
print(dir(self)) # Printing object contents after initializing name
def foo(self):
print("Before foo, self.name = "+ self.name)
self.name = 'John'
print("After foo, self.name = "+ self.name)
bar = Example('Jake')
bar.foo()
print(bar.name)
The output of the above code is
['__doc__', '__init__', '__module__', 'foo']
['__doc__', '__init__', '__module__', 'foo', 'name']
Before foo, self.name = Jake
After foo, self.name = John
John
I will walk you through this code. When we first create bar, the __init__()
method is called. Here we print the contents of the object using dir(self)
. The output ['__doc__', '__init__', '__module__', 'foo']
indicates that the object has only one member, the 'foo' method.
Now we create a new attribute called 'name' and assign it the value 'Jake'. Thus the object now has another member, the 'name' attribute as seen by the output of the next dir(self) ['__doc__', '__init__', '__module__', 'foo', 'name']
Now we call the foo method and print the value before and after the method. Before the name
is changed in foo, the value of name
associated with the object is "Jake". However, after the name
is changed, the value of self.name
is "John". This is indicated by
Before foo, self.name = Jake
After foo, self.name = John`
We next verify that the change made by changing self.name
has indeed changed the value of name
in bar
by printing bar.name
which gives us the expected output, John
Now coming back to your question, self.name
is not an ordinary variable inside some method that is lost when we are out of scope. self
can be used essentially anywhere inside the class to refer to the calling object (bar in this case). It is used to manipulate this calling object. Now since bar
is within the scope, we are able to print its name
attribute.
In normal functions any variables are forgotten but in methods it seems data is actually appended to the object itself
self
manipulates the attributes in the object and is not limited to the scope of the method.
Upvotes: 4
Reputation: 2557
First, you don't have to return a value in a method, not in python, and not in many other programming languages. for example:
def append_a(lst):
lst.append('a')
bob = []
append_a(bob)
print(bob)
['a']
Above we do not return anything in the function, but we use it to modify an existing data structure, this is very common almost anywhere.
Secondly, in your second example, you created an instance of the class Example, when you look at self.something
you are looking at a member of the class, unlike other languages, where often members are only declared once, in python you can dynamically add members.
Thus when looking at bar.name
you are looking at a member of the class, its value on the instance bar
. If you would look at a different instance, the value will be different.
class Example:
def __init__(self, name):
self.name = name
def foo(self):
self.name = 'John'
bar = Example('Jake')
bob = Example('Bob')
bar.foo()
print(bar.name)
print(bob.name)
John
Bob
Upvotes: 5