π‘ Problem Formulation: When delving into Python classes, newcomers may confuse the use of self
and __init__
. The issue arises with understanding why both exist and how they differ in terms of functionality. In this article, we will demystify these Python class components with distinct examples. We’ll show how the self
argument represents an instance of the class, while __init__
is a method that initializes the instance upon creation.
Method 1: Functionality of self
Within a Python class, self
represents the instance of the class, allowing access to the instanceβs attributes and methods. When you define a method within a class, self
is always the first parameter. It must be explicitly listed in the definition, but you do not pass it when calling the method. Consider it as a reference to the ‘current object’.
Here’s an example:
class MyClass: def __init__(self, value): self.attribute = value def show(self): print(self.attribute) obj = MyClass("Hello, self!") obj.show()
Output:
Hello, self!
In the above example, self
within the show
method refers to obj
, which is an instance of MyClass
. It is used to access the class attribute attribute
that was initialized in the __init__
method.
Method 2: The role of __init__ method
The __init__
method in Python is a special method also known as the constructor. It is called automatically when a new instance of a class is created. The primary role of __init__
is to initialize the state of an instance by assigning values to its properties or performing any setup procedures.
Here’s an example:
class MyClass: def __init__(self, value): self.attribute = value obj = MyClass("Initialized with __init__") print(obj.attribute)
Output:
Initialized with __init__
In this snippet, the __init__
method initializes the attribute
of the class instance obj
with the string passed as an argument. This is the first method called after an instance is created, setting the initial state of the object.
Method 3: When to use self vs __init__
Use self
to refer to and modify the state of a class instance within any instance method. Use __init__
to set up the initial state of an instance when the class is first instantiated. In other words, __init__
is only called once per instance, while self
is used throughout the class to refer to the specific instance within the class’s methods.
Here’s an example:
class MyClass: def __init__(self, value): self.attribute = value def update(self, new_value): self.attribute = new_value obj = MyClass("Initial Value") print(obj.attribute) obj.update("Updated Value") print(obj.attribute)
Output:
Initial Value Updated Value
The update
method uses self
to modify the object’s attribute
, showcasing self
in action outside of the __init__
method. We see that the class’s state can be altered beyond initialization with self
.
Method 4: Shared attributes and self
Class attributes, shared by all instances, are not typically associated with self
but can be accessed through it. Since self
is a reference to the instance, it provides a pathway to even class attributes, which aren’t bound to any one instance. On the other hand, instance attributes initialized in the __init__
method are unique to each instance and are directly associated with self
.
Here’s an example:
class MyClass: shared_attribute = "I am shared" def __init__(self): self.unique_attribute = "I am unique" obj1 = MyClass() obj2 = MyClass() print(obj1.shared_attribute) obj2.shared_attribute = "Changed shared" print(obj1.shared_attribute)
Output:
I am shared I am shared
This code illustrates that altering a perceived class attribute via self
does not affect the class attribute itself, but rather shadows it by creating a new instance attribute of the same name.
Bonus One-Liner Method 5: Chaining Methods with self
Using self
, you can chain methods within the class, enabling a fluent interface that allows multiple method calls in a single statement. This happens as each method returns the self
instance, facilitating the subsequent method calls.
Here’s an example:
class MyClass: def __init__(self): self.value = 0 def increment(self): self.value += 1 return self def display(self): print(self.value) obj = MyClass().increment().increment().display()
Output:
2
The chaining of increment
and display
methods demonstrates how returning self
can be used for method chaining, providing a concise and fluent way to manipulate object state and behavior.
Summary/Discussion
- Method 1: self as instance reference. Self represents the instance allowing attribute and method access. It cannot initialize attributes.
- Method 2: __init__ as initializer. The constructor method for setting up instances with their initial state. It is called only once per instance creation.
- Method 3: Usage context. Self is used throughout class methods while __init__ is specifically for initialization.
- Method 4: Shared vs. unique attributes. Self can access both shared (class) attributes and unique (instance) attributes. Changes via self usually affect only the instance.
- Bonus Method 5: Method chaining. Self allows for method chaining by returning the instance itself from methods, thus enabling fluent interfaces.