Lesson 1
Classes and Objects in Python
Welcome to Classes and Objects

Let's dive into a foundational concept in Object-Oriented Programming (OOP): Classes and Objects. If you have already explored OOP concepts in other programming languages or previous units, this might serve as a good reminder. If not, no worries, we'll start from the basics.

What is a Class?

In Python, a class is a blueprint for creating objects, encapsulating data (attributes) and functions (methods) to manipulate that data. Think of a class like a template. For example, consider a Person class, which might have attributes like name and age, and methods like display to showcase this information. The class defines the structure and behavior of the objects that are created from it, but it doesn't consume any memory until instances (objects) are created.

Defining a class doesn't automatically create objects; rather, it establishes a new data type that can be used to create multiple instances or objects. These objects, or instances, are created using the class and can have unique attribute values while sharing the same methods. For instance, Person is the class, and person1 and person2 are two distinct objects created from the Person class. Each object operates independently but follows the shared structure and behavior defined by the class.

Declaring and Defining Classes

In Python, a class is defined using the class keyword. Here's a simple example:

Python
1class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age

In this snippet, we define a Person class with data members name and age.

Methods

Methods in Python classes are functions that are defined inside the class and are used to manipulate the attributes of an instance or to perform operations related to the object. A method is called on an object and has access to the object’s attributes through the self parameter. All methods in Python should have self as the first parameter to refer to instance attributes and methods from within the method.

For example, consider a display method in the Person class that shows the person's details:

Python
1class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def display(self): 7 print(f"Name: {self.name}, Age: {self.age}")

In this example, the display method prints the name and age attributes of the Person object to the console. When you call person.display(), where person is an instance of Person, it invokes the display method and outputs the person’s details. The self parameter in the method ensures that it can access and modify the specific instance's attributes and behaviors.

Methods can also accept additional parameters to perform various operations. Here’s a method to update the age of a Person:

Python
1class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def display(self): 7 print(f"Name: {self.name}, Age: {self.age}") 8 9 def update_age(self, new_age): 10 self.age = new_age

In the update_age method, the new_age parameter is used to update the age attribute of the object. Calling person.update_age(31) changes the age to 31 for the person object.

This modular approach allows you to define functionalities specific to objects, enhancing code reusability and maintainability. By encapsulating behaviors within methods, you ensure that related operations are packaged together, making your code cleaner and easier to manage.

Understanding the Constructor

As you may have noticed, there is a special method named __init__ within the class. This method is called the constructor because it creates, or rather constructs, a new object of the class. It is automatically invoked when an object is instantiated. The constructor's main purpose is to initialize the object's attributes, and it can accept arguments to set initial values for these attributes.

In the provided Person class example:

Python
1class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age

The __init__ method has two parameters: name and age. When a new Person object is created, these parameters are used to set the name and age attributes of that object. For example:

Python
1person = Person("Alice", 30)

Here, the name attribute is set to "Alice" and the age attribute is set to 30. The self parameter in the constructor is a reference to the current instance of the class, allowing the method to access and initialize the object's attributes.

Understanding the interplay between classes, objects, and the constructor is fundamental to grasping the essence of Object-Oriented Programming in Python. This knowledge sets the stage for more advanced OOP concepts and best practices, ensuring you can create robust and manageable code.

Creating Objects from Classes

Once you have defined a class, you can create objects (instances of the class). Here’s how we can create and use objects of the Person class:

Python
1if __name__ == "__main__": 2 person = Person("Alice", 30) # Creating an object 3 person.display() # Print the object's data: "Name: Alice, Age: 30" 4 5 person_copy = Person(person.name, person.age) # Copying the object 6 person_copy.display() # Print the copied object's data: "Name: Alice, Age: 30"

Here, we create an object, person, with the name "Alice" and age 30, and another object, person_copy, which is a copy of the first object by passing the same name and age attributes to the constructor. Both objects display their data using the display method.

We run this code within the if __name__ == "__main__": block, which ensures that the code executes only if the script is run directly, not when it is imported as a module. This is considered good practice and is similar to the main function in other languages for organizing standalone or test code.

Instance Attributes

When building a class in Python, it's essential to understand the role of instance attributes. These attributes are specific to each object (instance) of the class and allow each instance to maintain unique data. They are defined within methods (usually within the __init__ method) and use the self keyword to be unique to each instance. For example, name and age in the Person class are instance attributes.

Python
1class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age
Class Attributes

In contrast to instance attributes, class attributes are shared across all instances of a class. These attributes are defined within the class, outside of any methods, providing a common value or configuration setting for all objects created from the class. All instances of the class share the same value for class attributes unless it is explicitly overridden.

Python
1class Person: 2 species = "Homo sapiens" # Class attribute shared by all instances 3 4 def __init__(self, name, age): 5 self.name = name 6 self.age = age

In this example, species is a class attribute. Every instance of the Person class will have species set to "Homo sapiens" by default.

Python
1person1 = Person("Alice", 30) 2person2 = Person("Bob", 25) 3 4print(person1.species) # Output: Homo sapiens 5print(person2.species) # Output: Homo sapiens 6 7Person.species = "Neanderthal" 8 9print(person1.species) # Output: Neanderthal 10print(person2.species) # Output: Neanderthal 11 12person1.species = "Chimpanzee" 13 14print(person1.species) # Output: Chimpanzee 15print(person2.species) # Output: Neanderthal

You can update the shared class attribute by accessing it through the class name, such as Person.species = "Neanderthal". However, if you assign a new value to species directly on an instance, it creates a unique instance attribute with that value, which will take precedence over the class attribute. This allows individual objects to have their own version of the attribute if needed. Additionally, you can access class attributes within instance methods using self, unless an instance attribute with the same name exists, in which case the instance attribute takes precedence. For example, within an instance method, you can use self.species to refer to the class attribute species if an instance attribute species does not override it.

Class attributes are valuable when you need a value or configuration setting that is the same across all instances of a class. They help to reduce redundancy and provide a shared state or behavior.

By understanding the distinction between class attributes and instance attributes, you can more effectively structure your classes to fit your program's needs. This differentiation is crucial as you design and implement more intricate class systems.

Conclusion

Understanding classes and objects is critical because they enable you to model real-world entities in your programs effectively. For instance, a Person class helps you create multiple person objects with different names and ages, enabling you to manage and manipulate data efficiently. This principle is the backbone of complex software systems.

By differentiating between instance attributes and class attributes, you can structure your classes to fit various program needs. Instance attributes maintain unique data for each object, while class attributes provide a shared value across all instances unless overridden.

With this foundational knowledge, you will be better prepared to approach advanced OOP techniques and design patterns. This will enhance your ability to write clean, modular, and scalable code. Let's get started with the practice section to gain more hands-on experience.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.