The concept of self in Python is fundamental to understanding object-oriented programming (OOP) within the language. While it might initially seem like an arcane keyword, self is, in fact, a convention that plays a crucial role in how Python objects interact with their own data and methods. At its core, self refers to the instance of the class itself. When you define a method within a class, the first parameter of that method is conventionally named self. This parameter acts as a placeholder for the specific object that the method is being called upon. Without self, Python wouldn’t know which instance’s attributes or methods to access or modify.

This might sound abstract, so let’s break it down. Imagine you have a blueprint for a drone. This blueprint (the class) defines what properties a drone can have (like battery level, GPS coordinates, flight mode) and what actions it can perform (like take off, land, hover). When you actually build a drone from this blueprint, you’re creating an instance of the drone class. Each individual drone you build is a distinct object, even though they are all based on the same blueprint.
Now, if you want to tell that specific drone to take off, you need a way to refer to it. In Python, self is that mechanism within the methods of the class. When you call my_drone.take_off(), Python implicitly passes the my_drone object as the self argument to the take_off method. This allows the take_off method to access and modify my_drone‘s specific attributes, such as its current altitude or motor status.
The Implicit Nature of self
One of the most significant aspects of self is its implicit passing. Unlike other arguments you might pass to a method, you don’t explicitly provide the self argument when calling a method on an object. Python handles this automatically. This design choice simplifies method calls and makes the code more readable.
Consider a simple Drone class:
class Drone:
def __init__(self, model):
self.model = model
self.battery_level = 100
def display_model(self):
print(f"This drone is a {self.model}.")
def decrease_battery(self, amount):
self.battery_level -= amount
print(f"Battery level is now {self.battery_level}%.")
In this class:
-
The
__init__method (the constructor) is called when a newDroneobject is created. It takesselfandmodelas arguments.self.model = modelassigns the providedmodelvalue to themodelattribute of the specificDroneinstance being created.self.battery_level = 100initializes thebattery_levelattribute for that instance. -
The
display_modelmethod takesselfas its only parameter. When you callmy_drone.display_model(), Python automatically passesmy_droneas theselfargument. Inside the method,self.modelrefers to themodelattribute ofmy_drone. -
The
decrease_batterymethod also takesself. When you callmy_drone.decrease_battery(10),my_droneis passed asself. The lineself.battery_level -= amountmodifies thebattery_levelattribute specifically for themy_droneobject.
Let’s see this in action:
my_drone = Drone("Phantom 4 Pro")
another_drone = Drone("Mavic 2 Zoom")
my_drone.display_model() # Output: This drone is a Phantom 4 Pro.
another_drone.display_model() # Output: This drone is a Mavic 2 Zoom.
my_drone.decrease_battery(25) # Output: Battery level is now 75%.
another_drone.decrease_battery(15) # Output: Battery level is now 85%.
print(f"My drone's battery: {my_drone.battery_level}%") # Output: My drone's battery: 75%
print(f"Another drone's battery: {another_drone.battery_level}%") # Output: Another drone's battery: 85%
This example clearly illustrates how self allows each instance to maintain its own unique state.
The Role of self in Attribute Access
The primary function of self is to provide access to an object’s instance attributes and methods. Any variable that is assigned using self.variable_name within a class method becomes an attribute of that specific instance. This is how each drone can have its own unique battery level, GPS coordinates, or firmware version, even if they are all of the same Drone model.
Instance Attributes vs. Class Attributes
It’s important to distinguish between instance attributes (accessed via self.attribute) and class attributes (defined directly within the class body, outside any method). Class attributes are shared by all instances of a class, whereas instance attributes are unique to each object.
class Drone:
# Class attribute (shared by all instances)
manufacturer = "DJI"
def __init__(self, model):
# Instance attributes (unique to each instance)
self.model = model
self.battery_level = 100
def display_info(self):
print(f"Model: {self.model}, Manufacturer: {self.manufacturer}")
In this enhanced example, manufacturer is a class attribute. All Drone objects will share the same manufacturer. model and battery_level are instance attributes, unique to each drone.
drone1 = Drone("Air 2S")
drone2 = Drone("Mini 3 Pro")
drone1.display_info() # Output: Model: Air 2S, Manufacturer: DJI
drone2.display_info() # Output: Model: Mini 3 Pro, Manufacturer: DJI
print(Drone.manufacturer) # Output: DJI (accessing class attribute directly)
print(drone1.manufacturer) # Output: DJI (accessing class attribute via instance)
Here, self.manufacturer within display_info refers to the shared class attribute.
self and Method Calls Within a Class
Just as self is used to access instance attributes, it’s also used to call other methods belonging to the same instance. This is crucial for structuring complex classes and breaking down functionality into smaller, reusable methods.

Imagine a Drone class with methods for different flight maneuvers:
class Drone:
def __init__(self, model):
self.model = model
self.altitude = 0
self.is_flying = False
def take_off(self):
if not self.is_flying:
print(f"{self.model} is taking off...")
self.altitude = 10
self.is_flying = True
self.log_flight_event("Takeoff initiated")
else:
print(f"{self.model} is already flying.")
def land(self):
if self.is_flying:
print(f"{self.model} is landing...")
self.altitude = 0
self.is_flying = False
self.log_flight_event("Landing initiated")
else:
print(f"{self.model} is already on the ground.")
def hover(self):
if self.is_flying:
print(f"{self.model} is hovering at {self.altitude} meters.")
self.log_flight_event(f"Hovering at {self.altitude}m")
else:
print(f"{self.model} cannot hover as it is not flying.")
def log_flight_event(self, event_message):
# This is a helper method to log events
print(f"[Flight Log] {event_message}")
In this Drone class, the take_off method calls self.log_flight_event("Takeoff initiated"). This means that when take_off is executed on a specific Drone instance, it also calls the log_flight_event method on that same instance. This ensures that the log message is associated with the correct drone’s flight history.
Let’s test this:
my_drone = Drone("Mavic Air")
my_drone.take_off()
# Output:
# Mavic Air is taking off...
# [Flight Log] Takeoff initiated
my_drone.hover()
# Output:
# Mavic Air is hovering at 10 meters.
# [Flight Log] Hovering at 10m
my_drone.land()
# Output:
# Mavic Air is landing...
# [Flight Log] Landing initiated
The use of self to call other methods within the class promotes code organization and reusability. It allows you to build complex behaviors by composing simpler ones.
Why Not Just Use a Different Name?
While self is the universally adopted convention in Python, you could technically use a different name for the first parameter of a method. For example:
class Drone:
def __init__(obj_ref, model): # Using obj_ref instead of self
obj_ref.model = model
def display_model(obj_ref): # Using obj_ref instead of self
print(f"This drone is a {obj_ref.model}.")
However, this is strongly discouraged.
- Readability:
selfis the standard. Any Python developer familiar with the language will immediately understand its purpose. Using a different name creates confusion and makes your code harder to read and maintain for others (and for your future self). - Tooling: Many IDEs and code analysis tools rely on the
selfconvention for features like code completion and static analysis. Using a non-standard name might break these tools. - Community Standards: Adhering to conventions like
selffosters a consistent and collaborative programming environment.
Think of self as a universally recognized pronoun for an object within its own scope. While you could invent a new pronoun, it would make communication incredibly difficult.
self in the Context of Inheritance
In object-oriented programming, inheritance allows a new class (a subclass or derived class) to inherit properties and behaviors from an existing class (a superclass or base class). self plays a vital role in managing this relationship.
When a subclass needs to extend or modify the behavior of its superclass, it often needs to call the superclass’s methods. This is done using super(), and self is implicitly involved in this process.
Consider a more specialized drone, like a “RacingDrone”:
class Drone:
def __init__(self, model):
self.model = model
self.battery_level = 100
def perform_action(self, action_name):
print(f"{self.model} is performing generic action: {action_name}")
class RacingDrone(Drone): # Inherits from Drone
def __init__(self, model, top_speed):
# Call the parent class's __init__ to initialize inherited attributes
super().__init__(model)
self.top_speed = top_speed
self.boost_active = False
def perform_action(self, action_name):
# Override the parent's method
if action_name == "boost":
self.boost()
else:
# Call the parent class's perform_action for other actions
super().perform_action(action_name)
def boost(self):
if not self.boost_active:
print(f"{self.model} engaging turbo boost! Top speed: {self.top_speed} mph")
self.boost_active = True
self.battery_level -= 10 # Boost consumes battery
else:
print(f"Turbo boost is already active on {self.model}.")
In the RacingDrone class:
-
In
__init__,super().__init__(model)calls the__init__method of theDroneclass. Python passes theRacingDroneinstance (represented byselfimplicitly in thesuper()call context) to theDrone.__init__method. This ensures thatself.modelandself.battery_levelare properly initialized for theRacingDroneinstance. -
In
perform_action,super().perform_action(action_name)calls theperform_actionmethod of theDroneclass. Again, theRacingDroneinstance is implicitly passed asselfto the parent’s method.
This demonstrates how self is crucial for navigating the inheritance hierarchy, ensuring that methods and attributes are correctly accessed and managed across different class levels.

Conclusion: The Cornerstone of Instance-Based Programming
self is not a keyword that you define or change; it’s a convention that Python relies on to distinguish between different objects of the same class. It’s the way an object refers to itself. Every time you call a method on an object, Python passes that object as the first argument to the method, and by convention, we name this first argument self. This allows methods to access and modify the object’s unique attributes and to call other methods on the same object.
Understanding self is indispensable for anyone looking to write object-oriented Python code, from creating simple data structures to building complex applications like drone control systems, flight simulators, or imaging analysis pipelines. It’s the silent, yet powerful, mechanism that makes object-oriented programming in Python work, ensuring that each object maintains its individuality and interacts correctly within its own context.
