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?
A Python function will always have a return value. There is no notion of procedure or routine in Python. So, if you don't explicitly use a return value in a return statement, or if you totally omit the return statement, then Python will implicitly return a default value for you.
The return statement makes a python function to exit and hand back a value to its caller. The objective of functions in general is to take in inputs and return something. A return statement, once executed, immediately halts execution of a function, even if it is not the last statement in the function.
In Python, it is possible to compose a function without a return statement. Functions like this are called void, and they return None, Python's special object for "nothing".
The print() function writes, i.e., "prints", a string or a number on the console. The return statement does not print out the value it returns when the function is called. It however causes the function to exit or terminate immediately, even if it is not the last statement of the function.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With