As you start your journey in Python programming, you might wonder, "If we already have functions in Python for bundling and reusing code, why do we even need Objects and Classes?" The answer lies in the added level of organization and abstraction that classes and objects provide, which is especially beneficial in larger, more complex programs.
Let's use an analogy, building houses. If functions are like individual tasks involved in building a house, such as laying bricks or installing windows, then a Class is like having a blueprint for the house, and Objects are the houses built from that blueprint. While each task, or function, is essential, without a blueprint (Class) to organize and coordinate these tasks, it would be very difficult to construct a cohesive and well-structured house (Object). By defining a class (blueprint), we can create as many objects (houses) as we want, all with the same basic structure but with their own individual attributes.
Think of Classes as the blueprint for a house. This blueprint defines all the characteristics a house can have, like the number of rooms, the colour of the walls, the type of flooring, and so on. Similarly, Classes in Python serve as a blueprint for creating Objects. They define properties (attributes) and behaviours (methods) that every instance of the class should have.
Objects are individual houses built using the blueprint. For instance, if 'House' is a Class, an Object might be 'myHouse' or 'yourHouse', each built using the 'House' blueprint but perhaps differing in the number of rooms or colour of walls. Likewise, in Python, an Object is an Instance of a Class, carrying its own set of attributes and methods defined by the Class.
Music Player Application
In this post, we'll be building a simple Music Player in Python. This
MusicPlayer Class will act as a basic model for a music player application. It will maintain a playlist of songs and will allow us to add and remove songs. We will build upon this foundation and explore the concepts of Classes, Objects, Methods, and attributes in Python's object-oriented programming. This hands-on approach will help us understand how these pieces work together to form a cohesive, functioning application.
In the next section, we'll dive into the creation of our
MusicPlayer class, and begin to explore the process of instantiation, which allows us to create individual music players, each with its own unique playlist. Let's get started!
In Python, a class is defined using the keyword
class, followed by the class name. Here's a simplified version of our
class MusicPlayer: def __init__(self): self.playlist = 
In the code above,
MusicPlayer is our class. The
__init__ method in Python is a special method that's automatically called when you create a new instance of a Class. It's often referred to as a constructor because it's used to set up or initialize attributes for a new object.
When we define the
__init__ method in a class, we typically use it to set the initial state of an instance. For example, in our
MusicPlayer class, we have an
__init__ method that initializes the
playlist attribute as an empty list. (New music players come with an empty playlist, right?)
Objects and Instantiation
To create an Object (or an instance) from a Class, we call the Class using its name followed by parentheses.
player = MusicPlayer()
In this case,
player is an Instance or Object of the
MusicPlayer class. The term "Instance" refers to an individual object of a certain Class. Instantiation is the process of creating an Object from a Class. Each object created this way is an Instance of that Class, possessing its own set of attributes and methods as defined by the Class blueprint.
Within a Class, we can define functions which are typically referred to as methods. Here's how we add a method to our
class MusicPlayer: def __init__(self): self.playlist =  def add_track(self, track): self.playlist.append(track)
add_track method takes a track name as an argument and appends it to the music player's playlist. This allows us to dynamically add new tracks to any instance of the
Scope and Instance Variables
You may already be familiar with two types of scope in Python - Global and Local. Global scope refers to variables defined outside all functions, and these variables can be accessed from anywhere in the code. Conversely, local scope refers to variables that are defined within a function, and these variables are only accessible within that function.
However, object-oriented programming (OOP) in Python introduces a third level of scope, known as Instance Scope. An instance variable, also called an attribute, is a variable that belongs to one instance of a class. Each object has its own copy of the instance variable, so changes to the variable in one object have no effect on other objects of the same class.
In a class definition, instance variables are initiated with
self.playlist in our
MusicPlayer class. These variables are accessible within any method in the class, giving them object-level scope. For instance,
self.playlist could be accessed and modified within any method of our
Methods in a Class can also have local variables, just like in a standalone function. These are temporary and exist only as long as the method is running. Unlike instance variables, they aren't prefixed with
self. Once the method exits, these variables go away.
Instance Variable Example
class MusicPlayer: def __init__(self): self.playlist =  # Instance variable def add_track(self, track): self.playlist.append(track) player = MusicPlayer() # Create a MusicPlayer instance player.add_track("Track 1") # Call the add_track method print(player.playlist) # Prints: ["Track 1"]
In this example,
self.playlist is an instance variable. It's defined in the
__init__ method, and it's accessed and modified in the
add_track method. This variable is tied to the instance of the
MusicPlayer class, represented by
When we create an instance of
player = MusicPlayer(),
player has its own
playlist attribute. We can add tracks to
player's playlist by calling
player.add_track("Track 1"), which adds "Track 1" to the playlist.
playlist variable belongs to the
player object, and its value is preserved even after the
add_track method has been called. We can verify this by printing
player.playlist after the method call, which returns ["Track 1"].
This is the key feature of instance variables: they belong to an instance of the class, and their values are preserved for as long as the instance exists.
Local Variable Example
class MusicPlayer: def __init__(self): self.playlist =  # Instance variable def add_track(self, track): track_to_add = track + " (added)" # Local variable self.playlist.append(track_to_add) player = MusicPlayer() # Create a MusicPlayer instance player.add_track("Track 1") # Call the add_track method print(player.playlist) # Prints: ["Track 1 (added)"]
In this version of the
track_to_add is a local variable. We create and use this variable within the
add_track method, and it goes out of scope (i.e., ceases to exist) when the method finishes running. If you try to print
track_to_add outside the method, you will get an error because it's not defined in that scope.
Functions vs Methods
You might be wondering about the terms 'functions' and 'methods' that have been used so far, here is how they differ from each other.
- Definition: A function is a piece of code that is called by its name and can be independent of a class, while a method is associated with an object and belongs to a class.
- Access to Data: A function operates on its input arguments and has access to the global scope, while a method has access to the instance and its associated attributes.
- Invocation: A function is called by its name independently, while a method is called on an instance of a class, such as
Keep in mind that these differences mainly pertain to the concept of OOP, and "function" and "method" can sometimes be used interchangeably in general coding conversations.
Build the Music Player
Now that we've established a solid understanding of the core principles of Python's object-oriented programming, let's put theory into practice. We're going to work with our
MusicPlayer class, a simplistic representation of a music player, allowing us to add and remove tracks, and control playback. This real-world example will help us visualize the concepts we've learned and see how they interact in a functional piece of code. Let's look at the entire code first and then we will break it down bit by bit.
class MusicPlayer: def __init__(self): self.playlist =  self.isPlaying = False self.currentTrack = None def add_track(self, track): self.playlist.append(track) def remove_track(self, track): if track in self.playlist: self.playlist.remove(track) def play(self): if self.playlist: self.currentTrack = self.playlist self.isPlaying = True def next_track(self): if self.playlist and self.currentTrack in self.playlist[:-1]: currentIndex = self.playlist.index(self.currentTrack) self.currentTrack = self.playlist[currentIndex + 1] def stop(self): self.isPlaying = False self.currentTrack = None
class MusicPlayer: def __init__(self): self.playlist =  self.isPlaying = False self.currentTrack = None
Here we're defining a class named
__init__ method is called when an instance of
MusicPlayer is created. It sets up the initial state of a
MusicPlayer object with three attributes:
currentTrack. These are instance variables, each
MusicPlayer object will have its own set of these variables.
Next, we have a set of methods that define the behaviors of a
def add_track(self, track): self.playlist.append(track)
add_track method adds a track to the playlist. It accesses the instance's playlist attribute with
self.playlist and appends the new track to it.
remove_track method removes a track from the playlist.
def remove_track(self, track): if track in self.playlist: self.playlist.remove(track)
stop methods control the playback.
def play(self): if self.playlist: self.currentTrack = self.playlist self.isPlaying = True
play method starts the playback if there are any tracks in the playlist, sets the
currentTrack to the first track in the playlist, and changes
isPlaying to True.
next_track method switches the current track to the next one in the playlist.
def next_track(self): if self.playlist and self.currentTrack in self.playlist[:-1]: currentIndex = self.playlist.index(self.currentTrack) self.currentTrack = self.playlist[currentIndex + 1]
self.currentTrack in self.playlist[:-1] - This checks if the current track is in the list of all tracks except the last one.
self.playlist[:-1] is a slicing operation that returns all elements in the playlist except the last one. This check is necessary because if the current track is the last one in the playlist, there is no "next track" to move to.
stop method stops the playback.
def stop(self): self.isPlaying = False self.currentTrack = None
Let's create an instance of
MusicPlayer and use these methods.
player = MusicPlayer() # Create a MusicPlayer instance player.add_track("Track 1") # Add "Track 1" to the playlist player.add_track("Track 2") # Add "Track 2" to the playlist player.play() # Start playing player.next_track() # Switch to the next track player.stop() # Stop playing
This example demonstrates the core concepts we've discussed: classes as blueprints for objects, instantiation of classes into objects, instance variables, and methods. Each
MusicPlayer object has its own state and behavior, and we can manipulate these through the methods defined in the class.
Creating Multiple Instances from the Same Class
One of the powerful features of Classes is that they allow us to create multiple objects, each with its own set of attributes. Let's illustrate this with our
# Creating two instances of MusicPlayer class player1 = MusicPlayer() player2 = MusicPlayer() # Adding tracks to the players player1.add_track("Track 1") player2.add_track("Track A") # Print the playlist of each player print(player1.playlist) # Prints: ["Track 1"] print(player2.playlist) # Prints: ["Track A"]
In the above code,
player2 are distinct instances of the
MusicPlayer class. Even though they are created from the same class, they have separate sets of attributes. When we add a track to
player1, it doesn't affect the playlist of
player2 and vice versa.
This ability to create multiple, independent instances from a class is a fundamental part of object-oriented programming and allows for great flexibility and reuse of code.
Passing Arguments to a Method
If you are familiar with functions, you might be wondering about the
add_track method in our
MusicPlayer class, which is defined with two parameters:
track. However, when we call this method, we only pass in one argument. Here's how it works.
When we call a method on an instance of a class, Python automatically passes the instance as the first argument. This argument is conventionally named
self. So, when we call
player1.add_track("Track 1"), Python is actually calling
add_track(player1, "Track 1") behind the scenes.
self parameter allows the method to access or modify the attributes of the instance on which it's called. That's why we use
self.playlist in the method definition. It refers to the playlist of the specific
MusicPlayer instance that we're adding a track to.
self is the bridge between the instance of the class and its class methods, allowing the methods to operate on the correct instance's attributes. So, even though it looks like we're passing only one argument when calling a method, Python is handling
self for us, ensuring the method works correctly on the instance it's called on.
When we create objects in real-world applications, it's common that each object needs to start with a unique state. Initialization parameters are the perfect tool for this.
MusicPlayer class, let's say we want each music player to be associated with a brand right when it's created. We can achieve this by modifying the
__init__ method to accept a
class MusicPlayer: def __init__(self, brand): self.brand = brand self.playlist =  self.isPlaying = False self.currentTrack = None def add_track(self, track): self.playlist.append(track) # ... rest of the code ...
Now, when we instantiate our
MusicPlayer class, we pass in the brand name.
apple_player = MusicPlayer("Apple") sony_player = MusicPlayer("Sony")
sony_player are instances of the
MusicPlayer class, each associated with a different brand. The
brand is passed as an argument during instantiation and is used to set the
brand attribute in the
In conclusion, I hope that this journey through Python's object-oriented programming using the
MusicPlayer class has helped to clarify these concepts. We've covered a lot of ground, from understanding classes, objects, and instance variables, to exploring methods, initialization parameters, and the important