Object-Oriented Programming (OOP) is a method of programming that models real-world things or processes as classes or objects in code. Essentially, it's about using code to create digital versions of everyday things like cars, users, casinos, processors, or calendars. In OOP, these real-world elements are represented as objects, each defined by a class, which acts like a blueprint. This blueprint outlines the specific characteristics (like color, size, or speed) and abilities (like starting, stopping, or calculating) of these objects. By doing this, OOP helps in organising and managing complex software, making it more intuitive and easier to work with.
Object-Oriented Programming (OOP) is a programming paradigm used across various programming languages. A class acts as a blueprint for objects, defining their properties (attributes) and behaviours (methods). The key concepts of OOP include: Encapsulation, Abstraction, Inheritance and Polymorphism.
In Python as well OOP can be explained in simple terms using everyday analogies. Here's a beginner-friendly explanation:
1. Objects: Imagine objects as everyday things you're familiar with. For instance, think of a car. In Python, an object is like that car. It has characteristics (like color, brand, speed) and abilities (like driving, stopping, honking).
2. Classes: If objects are cars, then a class is like the blueprint used to build that car. It defines what the car (object) will be like. A class in Python is a blueprint from which objects are created. It defines the properties (like color and brand) and methods (like drive and stop) that the objects (cars) will have.
3. Attributes: These are the characteristics of the object. In our car example, attributes are things like the color of the car, its brand, or its speed. In Python, attributes are data stored inside an object.
4. Methods: These are like the abilities or actions of the object. In the car example, driving, stopping, and honking are methods. In Python, methods are functions that are defined inside a class and can be used by the object.
5. Inheritance: This is like getting traits from parents. Imagine a new model of a car that inherits characteristics from a previous model but with some upgrades or changes. In Python, one class can inherit attributes and methods from another, making it easier to reuse code.
6. Encapsulation: Think of it as keeping the car’s mechanics hidden under the hood. You can drive the car without knowing the intricate details of how the engine works. In Python, encapsulation involves keeping the inner workings of a class hidden from the outside, exposing only what is necessary.
7. Polymorphism: This is like being able to use the same action in different ways. For example, the word ‘start’ might start a car, a computer, or a race. In Python, polymorphism allows methods to do different things based on which object is using them.
In short, OOP in Python is about creating reusable code (classes) that define the nature (attributes) and abilities (methods) of objects, with the added benefits of inheritance (reusing and extending classes), encapsulation (hiding details), and polymorphism (using methods in versatile ways).
Classes and Objects
Let's delve into Classes and Objects, which are fundamental to Object-Oriented Programming (OOP). Essentially, OOP revolves around the use of "objects," and these objects are created based on "classes." You can think of classes as templates or blueprints from which objects, the individual instances, are formed.
If you've had some experience with Python, you may not yet know how to create your own classes, but you're likely already using classes and objects. For instance, when you make a list, you're using an object that's created from the list class. Similarly, when you create a dictionary, it's an instance of the dictionary class. So, even without defining your own classes, you're engaging with fundamental OOP concepts in Python.
If you're using Python in the terminal (you can start this by opening the terminal application and typing python), try entering help(int). When you do this, you'll receive output that provides detailed information about the int class in Python. This command is a handy way to explore and understand more about built-in Python classes directly from the terminal.
When you use the help(int) command in Python, as mentioned earlier, it reveals the int class's details. This class is the blueprint for all integer objects in Python. The output shows various methods that are available for any integer object you create. These methods are functions that can perform specific operations on or with integer values. In your Python journey, you'll frequently encounter and utilize many of these methods. Common ones include mathematical operations and others like floor, invert, and rand, each serving a unique purpose in handling integer data. Understanding these methods can greatly enhance your ability to work effectively with integers in Python.
Building on the previous concept, let's explore another example using the help(list) command in Python. When you enter this command, it displays the structure and capabilities of the list class. This class serves as a template for all list objects in Python, detailing the functionalities that every list comes equipped with.
Among these functionalities are several commonly used methods such as append, index, copy, remove, count, insert, pop, and sort. These methods, predefined in the list class, are tools you'll regularly use to manipulate and interact with list objects in Python. By understanding these methods, you can effectively harness the full potential of lists in your Python projects.
Now, to create an instance of the list class, you can simply declare a variable like numbers and assign a list to it, for example: numbers = [10, 20, 30]. This line of code effectively creates a new list object from the list class, filled with the specified elements.
This example demonstrates our interaction with built-in classes in Python. Now, let's move forward and learn how to define our own custom classes.
But first , let's explore why we use Object-Oriented Programming (OOP). It's important to note that OOP doesn't necessarily provide new functionality or capabilities that can't be achieved with other programming approaches. In fact, many languages don't even support OOP, and you can certainly program effectively without it. So, the question arises: why choose OOP?
The answer lies in the way OOP allows us to organize and structure our code. OOP aligns closely with how we naturally think and categorize things in the real world. It lets us define clear specifications and create classifications for different entities. The primary aim of OOP is to encapsulate code into logical, hierarchical groupings using classes. This encapsulation makes the code more intuitive, manageable, and scalable. By using classes, we can group related properties and behaviours, making our code more organised and, in turn, easier to understand, maintain, and extend. In summary, OOP is a methodology that offers a structured and intuitive way of coding that mirrors our real-world understanding of categories and hierarchies.
Let's explore an example of Object-Oriented Programming (OOP) using vehicles. In this context, we can think of different types of vehicles as classes in OOP. Each vehicle type, like a Car, Bicycle, or Motorcycle, can be a separate class with its own attributes and methods.
Attributes: These are the properties of each class. For example:
A Car might have attributes like color, speed, and engineType.
A Bicycle could include color, speed, and gearCount.
A Motorcycle might feature color, speed, and hasSidecar.
Methods: These are actions or functions that each class can perform. For instance:
The Car class could have methods like start(), stop(), and accelerate().
The Bicycle class might include pedal(), brake(), and changeGear().
The Motorcycle class could have start(), stop(), and rev().
Each class encapsulates all the properties and behaviors specific to that type of vehicle. When you create an object, such as myCar from the Car class, it is an instance of that class, inheriting all its attributes and methods.
This approach to coding, using classes and objects, makes the software more structured, intuitive, and easier to manage, particularly in complex systems. The provided illustration visualizes this concept with the example of different vehicle classes, showing how OOP aids in the efficient organization and management of code.
Lets see one more example
Let's consider the concept of animals.
In OOP, each animal type can be represented as a "class." For instance, Dog, Cat, and Bird can be separate classes. Each class has its unique attributes and methods, which are like characteristics and actions specific to that animal.
Attributes: These are properties or characteristics of the class. For example:
A Dog might have attributes like color, size, and breed.
A Cat could have color, size, and favorite food.
A Bird might include color, size, and wingSpan.
Methods: These are actions or functions that the class can perform. For example:
The Dog class could have methods like bark(), eat(), and sleep().
The Cat class might have purr(), eat(), and sleep().
The Bird class could include sing(), eat(), and fly().
In this model, every class contains and manages the characteristics and actions unique to that particular animal type. For instance, when you instantiate an object such as myDog from the Dog class, you're creating a specific example of that class. This new instance, myDog, then possesses all the qualities and capabilities defined in the Dog class.
This method of structuring code brings a higher level of organization and clarity, as each class operates as an independent entity equipped with its distinct attributes and functions. This clarity becomes particularly beneficial in simplifying and streamlining the management of more complex programming systems.
Using the examples of Animals and Vehicles, we can easily explain two key concepts of Object-Oriented Programming (OOP): Abstraction and Encapsulation.
Abstraction
Abstraction is about focusing on what is necessary and hiding the unnecessary details. It's like looking at a complex system from a high level without worrying about the intricacies.
In the Animal Example: Abstraction lets us interact with different animals through a simple, common set of actions like eat(), sleep(), and makeSound(), without needing to know the details of how each animal performs these actions. For instance, makeSound() is abstract because the sound a Dog makes (bark) is different from a Cat (meow), but the action's concept remains the same.
In the Vehicle Example: Abstraction is evident in methods like start(), stop(), and accelerate(). Regardless of whether it's a Car, Bicycle, or Motorcycle, these methods provide a simplified interface to interact with each vehicle. The underlying mechanics of how a Car starts versus how a Bicycle starts are hidden from the user.
Encapsulation
Encapsulation involves bundling the data (attributes) and the methods (functions) that operate on that data into a single unit (class) and restricting access to some of an object's components.
In the Animal Example: Each animal class (like Dog, Cat, Bird) encapsulates its own data like color, size, breed, and methods. The inner workings of each method, like how eat() works for a Dog versus a Cat, are hidden inside the class. This encapsulation allows changes to be made to the Dog class without affecting the Cat class.
In the Vehicle Example: The details of how each vehicle operates are encapsulated within their respective classes. The Car class, for instance, contains all the properties (like engine type, color) and methods (like accelerate) relevant to a car. These details are hidden from the other classes like Bicycle or Motorcycle, ensuring that each class is a self-contained unit.
In summary, abstraction in these examples is about presenting a simple, high-level interface for interacting with different animals or vehicles, while encapsulation is about keeping each class as a self-contained unit with its own data and methods, safe from outside interference and misuse.
Unlocking Code Efficiency: A Tale of Two Styles – Procedural vs. Object-Oriented Programming in Python
Let's take a simple concept: managing a book in a library system. We'll first look at a non-OOP approach (procedural programming) and then the same concept using OOP in Python.
Non-OOP Approach (Procedural Programming)
In procedural programming, we might use separate variables and functions.
# Variables
book_title = "The Great Gatsby"
book_author = "F. Scott Fitzgerald"
book_genre = "Novel"
book_is_checked_out = False
# Functions
def display_book(title, author, genre):
print(f"Title: {title}, Author: {author}, Genre: {genre}")
def check_out_book():
global book_is_checked_out
book_is_checked_out = True
def return_book():
global book_is_checked_out
book_is_checked_out = False
# Using the functions
display_book(book_title, book_author, book_genre)
check_out_book()
OOP Approach
In OOP, we encapsulate the data and functions into a class.
class Book:
def __init__(self, title, author, genre):
self.title = title
self.author = author
self.genre = genre
self.is_checked_out = False
def display_book(self):
print(f"Title: {self.title}, Author: {self.author}, Genre: {self.genre}")
def check_out_book(self):
self.is_checked_out = True
def return_book(self):
self.is_checked_out = False
# Creating an instance of Book
my_book = Book("The Great Gatsby", "F. Scott Fitzgerald", "Novel")
my_book.display_book()
my_book.check_out_book()
Advantages of OOP over Procedural Programming
1. Encapsulation: In OOP, data and methods are encapsulated within classes, making the code more organized.
2. Reusability: OOP's class structure allows for reusing and extending code easily.
3. Scalability: Managing and scaling complex programs is more straightforward with OOP.
4. Maintenance: OOP makes it easier to maintain and modify code, as changes in one part of the program do not affect other parts.
Public and Private in Python
In Python, attributes and methods are public by default, meaning they can be accessed from outside the class. Python doesn't have true private members, but it has a convention to indicate that a member should be treated as private.
Public: Can be accessed from outside the class.
self.title = "Public Title"
Private: Indicated by a single or double underscore prefix. They should not be accessed from outside the class.
self._private_title = "Private Title"
In the OOP example above, attributes like title, author, genre, and is_checked_out are public, meaning they can be accessed and modified directly. If we wanted to make them private, we would prefix them with double underscores. This is more about a convention and helping to prevent accidental access rather than strict enforcement as in some other languages.
Crafting Our First Python Class and Bringing Instances to Life
Having covered the theoretical aspects, let's dive into writing our very first Python class.
Let's create a simple Student class with no methods or attributes (empty class) and then create two instances (objects) of this class. We will also see what the output would look like when we execute this code.
The Student Class
class Student:
pass
Here, the Student class is defined using the class keyword followed by the class name Student. The pass keyword is used in Python to indicate an empty block of statements.
Creating Objects of the Student Class
student1 = Student()
student2 = Student()
We create two objects, student1 and student2, of the Student class. This is done by calling the class name as if it were a function.
Displaying the Objects
print(student1)
print(student2)
Expected Output
The output will show the memory addresses of the two Student objects, which will be different for each object. It might look something like this:
<__main__.Student object at 0x000001>
<__main__.Student object at 0x000002>
Explanation
Empty Class: An empty class like our Student class doesn't do much. It's like a placeholder for something more complex that you might define later. It has no attributes or methods.
Creating Objects: When we create instances of a class, we're essentially creating unique objects that are of the type Student. Even though Student is empty, Python allows creating instances of it.
Memory Addresses: The output represents the memory addresses where these objects are stored. Each object has a unique location in memory.
Usefulness: While an empty class might not seem useful, it can be a starting point in a larger program where you plan to add more details to the class later on.
This example demonstrates the basics of class creation and object instantiation in Python, forming a foundation for understanding more complex OOP concepts.
Building upon our previously created empty Student class, let's now enhance it by adding some attributes. This will give our Student class more functionality and allow it to represent student data more realistically.
Let's start by adding only the attributes to our class initially, and then progressively introduce methods later on. This incremental approach helps in understanding each part of the class construction process.
Defining the Student Class with Attributes Only
First, let's define the Student class with just the basic attributes: name, age, and courses. We'll initialize these attributes in the init method. We'll soon delve into the importance of the init method, a key aspect of our discussion just around the corner.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.courses = [] # Initializes an empty list for courses
Explanation
Class Definition: We start by defining the class Student with the class keyword.
The init Method: This special method is the class constructor. It initializes each new instance of the class.
Attributes:
self.name and self.age: These store the student's name and age, respectively. We set them based on the values passed when a new Student object is created.
self.courses: This is an empty list to store the courses the student enrolls in. We start with an empty list, as students typically enroll in courses after the object is created.
Creating Instances of Student
Now, let's create two instances of the Student class to see how this works.
student1 = Student("Alice", 20)
student2 = Student("Bob", 22)
Displaying the Objects
To display these instances and verify our attributes, we can use simple print statements. Note: We'll replace this with a proper method later.
print(f"Student Name: {student1.name}, Age: {student1.age}, Courses: {student1.courses}")
print(f"Student Name: {student2.name}, Age: {student2.age}, Courses: {student2.courses}")
Expected Output
The output will show the names, ages, and empty course lists for each student:
Student Name: Alice, Age: 20, Courses: []
Student Name: Bob, Age: 22, Courses: []
Optional Section: Executing the Student Class Code - Skip Ahead if You're Already Familiar with Running Python Scripts
How to run this code using a python script.
To run the Student class code, you can use a Python script.
1. Create a Python File:
Use a text editor (like Notepad, Visual Studio Code, or Sublime Text) to create a new file.
Save the file with a .py extension, for example, student_test.py.
2. Write the Code:
Copy the entire Student class definition into the file.
After the class definition, create instances of the class and add the print statements to display the attributes.
Your code could look like below.
Example Script (student_test.py)
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.courses = []
# Creating instances
student1 = Student("Alice", 20)
student2 = Student("Bob", 22)
# Printing attributes
print(f"Student Name: {student1.name}, Age: {student1.age}, Courses: {student1.courses}")
print(f"Student Name: {student2.name}, Age: {student2.age}, Courses: {student2.courses}")
3. Run the Script:
Open Command Prompt or Terminal, navigate to the directory where your script is saved using the cd command.
Run the script by typing python student_test.py (or python3 student_test.py on macOS/Linux).
After running this script you should see the output displaying the names, ages, and empty course lists for student1 and student2. This will confirm that your Student class is functioning as expected.
In the next steps, we'll expand this class by adding methods to manipulate the data.
Adding Methods to the Student Class
When we have defined the Student class with just attributes, we are organizing pieces of data that describe a student, such as their name, age, and enrolled courses. However, to truly harness the power of Object-Oriented Programming (OOP), we add instance methods to our class. These methods enable us to define actions or behaviors that each object of the class can perform.
Why Add Instance Methods?
Functionality: While attributes provide data, methods bring functionality. They enable objects to not just hold data but also to perform tasks and interact with other parts of the program.
Organizing Code: Methods help keep our code organized. Each method performs a specific task, making the code more modular and easier to manage.
Understanding Instance Methods
Method Structure: A method in a class is defined using the def keyword, followed by the method name and parentheses. For instance, the enroll method in our Student class is specific to each Student object.
def enroll(self, course):
# Method body
The self Parameter: Every method in a class has self as its first parameter. This is a convention in Python and is crucial for accessing the attributes within the class. self represents the instance on which the method is called. Without self, you cannot access the instance attributes inside the methods.
Accessing Attributes: Inside a method, we use self to access and modify the object's attributes. For example, self.name refers to the name attribute of that specific instance.
Methods Can Have Parameters: Besides self, methods can take additional parameters. In the enroll method, course is a parameter that represents the course to be added to the student’s list of courses.
Methods and Functionality: When you create a Student object, it comes with these methods. For example, calling student1.enroll("Math") will use the enroll method of the student1 instance to add "Math" to its courses.
This class will now include some basic attributes that a student might have, such as name, age, and a list of courses they're enrolled in. Lets also add methods to enroll in a new course and display the student's details.
Here's how the updated class will look:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.courses = []
def enroll(self, course):
self.courses.append(course)
print(f"{self.name} has enrolled in {course}.")
def display_student_info(self):
print(f"Student Name: {self.name}")
print(f"Age: {self.age}")
print("Courses Enrolled:")
for course in self.courses:
print(f"- {course}")
Explanation
enroll Method:
In Python, many data types, including lists, are actually implemented as classes. A list is not just a simple collection of items; it's an instance of the list class, which comes with a set of predefined methods that you can use to manipulate the list.
When you create a list, like my_list = [1, 2, 3], you're creating an instance of the list class. This instance (my_list) has access to various methods defined in the list class. One of these methods is append. The append method is defined within the list class and is used to add an item to the end of the list.
my_list = [1, 2, 3]
my_list.append(4)
print(my_list)
Just like how we use the append method on a list to add an item, in OOP, we define methods like enroll to add functionality to our objects. This method allows a student to enroll in a new course. It takes `course` as an argument and appends it to the student's courses list. It prints a confirmation message showing which course the student has enrolled in.
display_student_info Method:
This method is an excellent example of OOP functionality.When you call student1.display_student_info(), it accesses student1's name, age, and courses attributes and prints them. This method is unique to each Student instance; it displays the data of the specific student on whom it is called. It loops through the courses list to display each enrolled course.
Using the Updated Class
To use this updated class with the new methods:
1. Create Student Instances:
student1 = Student("Alice", 20)
student2 = Student("Bob", 22)
2. Enroll in Courses:
student1.enroll("Mathematics")
student2.enroll("Biology")
3. Display Student Information:
student1.display_student_info()
student2.display_student_info()
Expected Output
When you run this code, you should see output like:
Alice has enrolled in Mathematics.
Bob has enrolled in Biology.
Student Name: Alice
Age: 20
Courses Enrolled:
- Mathematics
Student Name: Bob
Age: 22
Courses Enrolled:
- Biology
Tip: To execute this updated student_test.py file with the added methods, simply follow the previous steps outlined for running the Python script. This will allow you to see the Student class in action with its newly integrated functionalities.
Updated student_test.py Script
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.courses = []
def enroll(self, course):
self.courses.append(course)
print(f"{self.name} has enrolled in {course}.")
def display_student_info(self):
print(f"Student Name: {self.name}")
print(f"Age: {self.age}")
print("Courses Enrolled:")
for course in self.courses:
print(f"- {course}")
# Creating instances of the Student class
student1 = Student("Alice", 20)
student2 = Student("Bob", 22)
# Enrolling students in courses
student1.enroll("Mathematics")
student2.enroll("Biology")
# Displaying student information
student1.display_student_info()
student2.display_student_info()
By following these steps, you'll be able to see how the Student class operates with both attributes and methods, offering a practical example of Python's Object-Oriented Programming capabilities.
Now, let's delve into some best practices related to class definition, particularly focusing on naming conventions and casing:
The class Keyword
Definition: A class in Python is defined using the class keyword, followed by the class name and a colon. The class name is typically a noun because it represents an entity.
class ClassName:
# class body
Naming Conventions and Casing
Class Names:
Convention: In Python, class names follow the 'CapWords' convention (also known as CamelCase). Each word in the class name starts with a capital letter, and there are no underscores between words.
Examples: Student, CreditCard, ElectricCar.
Method Names:
Convention: Method names should be lowercase with words separated by underscores (snake_case). This makes them easy to read and distinguishable from class names.
Examples: calculate_interest, get_details, set_value.
Instance Variable Names:
Convention: Like methods, instance variable names should also be in lowercase and separated by underscores.
Examples: account_balance, max_speed, birth_date.
Constants:
Convention: Constants in classes should be in all uppercase letters with words separated by underscores.
Examples: MAX_SPEED, DEFAULT_COLOR.
Other Best Practices
Docstrings: Always document classes and their methods using docstrings. This practice is crucial for maintainability and readability.
class MyClass:
"""This is a docstring for the MyClass."""
Explicit self: The first parameter of a method in a class should always be self, which refers to the instance calling the method.
Private Members: Use a single underscore prefix for 'protected' members and a double underscore prefix for 'private' members (though Python doesn't enforce strict encapsulation).
Avoid using too many instance variables: Too many attributes can make the class complex. Try to keep your class design simple and focused.
Inheritance: Use inheritance to reduce code redundancy, but also be cautious about creating deep inheritance hierarchies, which can make the code less readable.
Adhering to these naming conventions and best practices helps make your Python code more standardized, understandable, and maintainable.
Understanding the init Method in the Student Class
Let's take another look at the Student class example to illustrate the init method, providing a clear understanding in this context. This is a special method in Python, called a constructor. It's used for initializing new objects from the class.
Let's say you buy a new smartphone. When you turn it on for the first time, you have to set it up by entering your language, time zone, and personal details. The init method in Python does something similar for new objects. It sets up each new object with its initial state by assigning values to its properties.
class Student:
def init(self, name, age):
self.name = name
self.age = age
self.courses = []
In this Student class:
init Method: This method is like the setup procedure for each new Student object. It's automatically called when a new Student object is created.
Parameters and Attributes:
self: Refers to the newly created object. It's how the object keeps track of its own individual data.
name and age: These are parameters passed to init when creating a new Student. They are used to set the object's attributes.
self.name and self.age: These are attributes of the Student object. They are set to the values passed during the object's creation.
self.courses: This is another attribute, representing the courses the student is enrolled in. It's initialized as an empty list.
Creating a Student Object
alice = Student("Alice", 20)
When we create a new Student object like this:
Python calls the init method of the Student class.
alice is automatically passed as the self argument.
"Alice" and 20 are passed as the name and age.
The init method sets alice.name to "Alice", alice.age to 20, and initializes alice.courses as an empty list.
What Happens Behind the Scenes
Initialization: When alice = Student("Alice", 20) is executed, the init method initializes the alice object with the specified name and age.
Unique Attributes: Each Student object you create will have its own set of name, age, and courses attributes.
In essence, the init method is crucial for setting up each new Student object with its initial state, defining what data it holds, and preparing it for further use in the program.
Notice Self above. Lets understand it.
In Python, when you're working with Object-Oriented Programming (OOP), self plays a crucial role in how we define and interact with methods within a class.
What is self?
Think of a class as a blueprint for creating objects (like a blueprint for a house), and self as a reference to the specific instance (or object) created from that class (like a specific house built from that blueprint).
Why is self necessary?
To Reference the Instance: When you create an instance of a class (like my_dog = Dog()), self in the class's methods refers to that specific instance (my_dog). This way, each object keeps track of its own data and behavior.
To Access and Modify Object Attributes: self is used to access or modify attributes of the object. For example, if your Dog class has an attribute name, you use self.name to refer to the name of the specific dog you're dealing with.
How is self used?
In Method Definitions: In class methods, self is always the first parameter, but it's not passed explicitly when you call the method. Python takes care of that.
class Dog:
def __init__(self, name):
self.name = name # 'self.name' is an attribute of class
def bark(self):
print(f"{self.name} says Woof!") # 'self' refers to the specific Dog instance
2. In Method Calls: When you call a method of an object, you don’t pass self:
my_dog = Dog("Buddy")
my_dog.bark() # Python automatically passes 'my_dog' as 'self' to the bark method
Let's also revisit the book example to understand how self is used in Python's Object-Oriented Programming (OOP).
In the book example, we defined a Book class. This class is a blueprint for creating book objects, each with its own title, author, genre, and check-out status. Here's how self fits into this:
class Book:
def __init__(self, title, author, genre):
self.title = title
self.author = author
self.genre = genre
self.is_checked_out = False
The init method uses the self parameter as a reference to the specific object being created.
In our Book class example, attributes like self.title, self.author, self.genre, and self.is_checked_out are defined within the init method. These attributes are assigned to each unique book object that's created.
For example, when you create a book instance with:
my_book = Book("The Great Gatsby", "F. Scott Fitzgerald", "Novel")
you're essentially initializing a new Book object with its specific title, author, and genre.
Moreover, the self keyword is also used in other methods of the class, such as display_book(self)
def display_book(self):
print(f"Title: {self.title}, Author: {self.author}, Genre: {self.genre}")
Here, self refers to the specific instance of the class that's being used. So, when you call my_book.display_book(), the display_book method is applied to the my_book instance, accessing its title, author, and genre attributes through self.title, self.author, and self.genre. This approach illustrates how self serves as a link to the individual attributes and methods of a specific object, ensuring each object maintains its unique state and behavior.
Summary
self is like saying “this particular book” inside the class definition. It allows each book object to keep track of its own title, author, genre, and whether it is checked out or not.
It ensures that when you create multiple book objects, each one knows its own details and behaves independently of the others. You can create multiple instances of a class (like many different dogs), and each one will remember its own details (like each dog knowing its name). This is a fundamental aspect of making classes and objects useful in Python.
It's automatically handled by Python, making your code more intuitive and organized.
Exercise 1
Define a Car class with the following attributes:
make (the manufacturer of the car, like "Toyota")
model (the model of the car, like "Corolla")
year (the year of manufacture, like 2020)
color (the color of the car, like "red")
Instructions:
Use the class keyword to define the Car class.
Define the init method with self and the above attributes as parameters.
Inside the init method, assign these parameters to self.make, self.model, self.year, and self.color
Test Code to Verify the Class
After defining the class, use the following test code to check if their Car class is set up correctly.
# Create an instance of the Car class
my_car = Car("Toyota", "Corolla", 2020, "red")
# Print the attributes to verify
print(f"Make: {my_car.make}")
print(f"Model: {my_car.model}")
print(f"Year: {my_car.year}")
print(f"Color: {my_car.color}")
Expected Output
If the class is defined correctly, the output should be:
Make: Toyota
Model: Corolla
Year: 2020
Color: red
Exercise 2
Improve the Car class by adding instance methods that interact with its attributes. Specifically, add methods to update the color of the car and to display its full details.
change_color - Changes the color of the car.
display_car_info - Displays detailed information about the car.
Instructions:
Define the change_color method. It should take a new color as a parameter (besides self) and update the color attribute of the car.
Define the display_car_info method. This method should print the car's make, model, year, and color.
Create an instance of the Car class, use the change_color method to change its color, and then use display_car_info to display the updated details.
Example Code to Start:
class Car:
def __init__(self, make, model, year, color):
self.make = make
self.model = model
self.year = year
self.color = color
# Define change_color and display_car_info methods here
Test Code to Verify the Class
# Create an instance of the Car class
my_car = Car("Toyota", "Corolla", 2020, "red")
# Changing the color of the car
my_car.change_color("blue")
# Displaying the updated car information
my_car.display_car_info()
Expected Output
Make: Toyota, Model: Corolla, Year: 2020, Color: blue
Introducing Class Attributes and Class Methods
Until now, we've been working with instance attributes and methods in our classes, like the Car class. These are tied to individual objects created from the class. Let's now shift our focus to class attributes and class methods, which are different and used less frequently than instance attributes and methods.
Class Attributes
Class attributes are variables that are shared across all instances of a class. Unlike instance attributes, which can have different values for each object, class attributes maintain the same value for every instance of the class.
Why Use Class Attributes?
Class attributes are useful when you have a property that should be consistent and shared across all instances. They're typically used for constants related to the class, or properties that are common to all objects of that class.
Where They Are Defined:
Class attributes are defined directly inside the class, but outside of any instance methods (including init).
This positioning means that class attributes are not tied to any particular instance of the class. Instead, they are shared across all instances.
Scope and Accessibility:
Class attributes are accessible from both the class itself and all of its instances.
Modifying a class attribute affects it for all instances of the class. This is different from instance attributes, which are independent for each instance.
Let's extend our Car class example by adding a class attribute.
class Car:
wheels = 4 # Class attribute
def __init__(self, make, model, year, color):
self.make = make # Instance attribute
self.model = model
self.year = year
self.color = color
# Accessing the class attribute
print(Car.wheels) # Output: 4
# Accessing the class attribute through an instance
my_car = Car("Toyota", "Corolla", 2020, "red")
print(my_car.wheels) # Output: 4
Explanation:
wheels is a class attribute, common to all cars. We assume that every car has 4 wheels, so this attribute is shared by all instances of the Car class.
Both the class itself (Car.wheels) and instances of the class (my_car.wheels) can access this attribute.
Exploring Shared Memory: Understanding Class Attributes in Python with the `Car` Class Example
When a class variable is defined in a class, it is allocated a specific location in memory. Unlike instance variables, which are stored separately for each instance, a class variable is stored only once, regardless of how many instances of the class exist.
All instances of the class have access to the same class variable. They all reference the same memory location where the class variable is stored. This means that if one instance changes the value of the class variable, the change is reflected across all other instances.
class Car:
wheels = 4 # Class attribute
def __init__(self, make, model):
self.make = make # Instance attribute
self.model = model
# Creating two instances of the Car class
car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
# Print the memory addresses of the 'wheels' attribute
print("Memory address of 'wheels' for car1:", hex(id(car1.wheels)))
print("Memory address of 'wheels' for car2:", hex(id(car2.wheels)))
print("Memory address of 'wheels' in the Car class:", hex(id(Car.wheels)))
Output
Memory address of wheels for car1: 0x956ea0
Memory address of wheels for car2: 0x956ea0
Memory address of wheels in Car class: 0x956ea0
Explanation
All three memory addresses are the same, indicating that car1, car2, and even the Car class itself are referring to the same wheels attribute in memory.
This confirms that the wheels class attribute is a single entity shared across all instances of the Car class.
This output provides clear proof that class attributes are shared and exist in a single location in memory, accessed by all instances of the class.
Understanding Class Variables in Python: Access, Modification, and the Shadowing Effect
Accessing and Modifying Class Variables
Using the Class Name: As shown above you can access and modify a class variable directly using the class name. This approach ensures you are interacting with the class variable itself.
Car.wheels = 6
2. Using self Keyword: When you access a class variable within an instance method using self, you're still referring to the class variable shared by all instances. However, modifying it using self changes the behaviour.
Shadowing with Instance Variables
Creating a New Instance Variable: If you assign a value to a class variable using self within an instance method, Python doesn't actually modify the class variable. Instead, it creates a new instance variable with the same name for that specific instance. This new instance variable shadows the class variable. It means that when you try to access that variable using self, Python will use the instance variable, not the class variable.
class Car:
wheels = 4 # Class variable
def __init__(self, make):
self.make = make
def change_wheels(self, number):
self.wheels = number # Creates a new instance variable
# Creating an instance and modifying wheels
car1 = Car("Toyota")
car1.change_wheels(6)
print(Car.wheels) # Outputs: 4, class variable remains unchanged
print(car1.wheels) # Outputs: 6, instance variable for car1
In this example:
Changing wheels via car1.change_wheels(6) creates a new instance variable wheels for car1.
The class variable Car.wheels remains unchanged at 4.
Any reference to wheels through car1 now points to the new instance variable, not the class variable.
Understanding this behaviour is essential for correctly managing class and instance variables in Python and avoiding unintentional shadowing.
Class Methods
Class methods are methods that are bound to the class rather than its objects. They can modify class state that applies across all instances of the class.
Why Use Class Methods?
Class methods are used when a method needs to affect the class as a whole, not just individual instances. Their usage, while not as frequent as instance methods, is important only in certain scenarios. This is primarily because many classes are designed around instances and their individual states rather than the state or behavior of the class as a whole. They are often used for factory methods that create instances of the class using different input parameters than the constructor.
We can add a class method to our Car class as an example.
class Car:
wheels = 4
def __init__(self, make, model, year, color):
self.make = make
self.model = model
self.year = year
self.color = color
@classmethod
def from_string(cls, car_string):
make, model, year, color = car_string.split(',')
return cls(make, model, int(year), color)
# Using the class method to create a new instance
car_info = "Honda,Civic,2019,blue"
new_car = Car.from_string(car_info)
print(new_car.make) # Output: Honda
Explanation:
from_string is a class method that takes a string and returns a new instance of Car.
The @classmethod decorator is used to define a class method.
cls is a common convention for the first parameter of class methods, similar to self in instance methods. It refers to the class itself.
Another Example
Consider a User class where you might use a class method to create a user from data stored in a different format:
class User:
def __init__(self, username, email):
self.username = username
self.email = email
@classmethod
def from_json(cls, json_data):
return cls(json_data['username'], json_data['email'])
In this example, from_json is a class method used to create a User instance from JSON data, offering an alternative way to instantiate the class.
Understanding cls in Class Methods
What is cls?
cls is a parameter in class methods that represents the class itself, not an instance of the class. It is similar to self in instance methods, but while self refers to the specific object, cls refers to the class that defines the method. It's used to access class attributes or to call other class methods. It's also commonly used in factory methods to create new instances of the class.
When a class method is called, Python automatically passes the class (not the instance) as the first argument to the method. That's why the first parameter of a class method is often named cls, to signify that it receives the class itself.
Example
A Car Class with Class Attributes and Methods
class Car:
total_cars = 0 # Class attribute
def __init__(self, make, model):
self.make = make # Instance attribute
self.model = model
Car.total_cars += 1
@classmethod
def get_total_cars(cls):
return cls.total_cars
def display_car_info(self):
print(f"Car Make: {self.make}, Model: {self.model}")
and when we use this class definition like below
# Creating car instances
car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
# Displaying information of each car
car1.display_car_info()
car2.display_car_info()
# Getting the total number of cars created
print("Total cars:", Car.get_total_cars())
We expect following output:
Car Make: Toyota, Model: Corolla
Car Make: Honda, Model: Civic
Total cars: 2
Explanation:
Instance and Class Interaction: When car1 and car2 are created, the class attribute total_cars is incremented twice, reflecting two instances. This demonstrates how instances interact with class attributes.
Accessing Class Attributes: The class method get_total_cars accesses the total_cars attribute using cls.total_cars, which is the same as Car.total_cars.
Individual Instance Data: Each Car instance, car1 and car2, holds its own make and model, showcasing how instance attributes store individual data.
Here's an another example of a Student class in Python, demonstrating the usage of class methods for creating instances and performing class-level operations:
class Student:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def full_name(self):
return f"{self.first_name} {self.last_name}"
def birthday(self):
self.age += 1
return f"Happy {self.age}th birthday, {self.first_name}!"
@classmethod
def from_string(cls, data_string):
first_name, last_name, age = data_string.split(',')
return cls(first_name.strip(), last_name.strip(), int(age.strip()))
# Example usage
data_string = "Jack,Jones,49"
jack = Student.from_string(data_string)
print(jack.full_name()) # Output: Jack Jones
print(jack.birthday()) # Output: Happy 49th birthday, Jack!
In the Student class example, the from_string() method provides a convenient way to make class instances from data in a particular format. It accepts a string containing student information in the form "first_name,last_name,age" and generates a Student object based on that.
Rather than manually sorting through the string data and building a Student object with separate attributes, people using the class can just use the from_string() method. They input the data string, and the method takes care of the rest. This simplifies the creation of Student instances, especially when managing many instances or large datasets from external sources, leading to more concise and error-resistant code.
In this example:
We define a Student class with attributes first_name, last_name, and age, along with methods full_name() and birthday() to get the full name of the student and wish them a happy birthday.
The from_string() class method is decorated with @classmethod. It takes a string in the format "first_name,last_name,age" and creates a new instance of the Student class based on that data.
We create an instance of the Student class (jack) by calling the from_string() class method with the provided data string.
We then demonstrate the usage of the instance methods (full_name() and birthday()) to access and manipulate the attributes of the jack object.
Comentarios