Advanced Python for Practical Applications
Object-Oriented Programming
Thinking in Objects
Most programming you've likely done involves writing procedures or functions that operate on data. Object-Oriented Programming, or OOP, flips that around. It organizes code around objects, which bundle together data and the functions that operate on that data.
Think of it like a blueprint for a car. The blueprint isn't a car itself, but it defines what a car is: it has attributes (like colour, make, model) and it can perform actions (like start, stop, accelerate). In OOP, this blueprint is called a class.
An actual car built from the blueprint is an object (also called an instance). You can build many cars from one blueprint, and each one is a unique object with its own specific attributes (a red Ford, a blue Honda), but they all share the same fundamental structure and capabilities defined by the blueprint.
Object-Oriented Programming organizes software design around data, or objects, rather than functions and logic.
Creating Classes and Objects
Let's translate the car blueprint idea into Python code. We define a class using the class keyword. This creates our blueprint.
class Car:
# This is a special method called the constructor
def __init__(self, color, make):
# These are instance attributes
self.color = color
self.make = make
# This is an instance method
def start_engine(self):
return f"The {self.color} {self.make}'s engine is running."
Here, Car is the class. The __init__ method is a special method that runs when we create a new object from the class. It's called a constructor. It initializes the object's attributes. Notice the self parameter. It refers to the specific instance of the class that is being created or used.
Now, let's build a couple of cars from our Car blueprint. Creating an object from a class is called instantiation.
my_car = Car("Red", "Tesla")
your_car = Car("Blue", "Rivian")
# Accessing attributes
print(my_car.color) # Output: Red
# Calling methods
print(your_car.start_engine()) # Output: The Blue Rivian's engine is running.
Attributes and Methods
Objects have two main components: attributes and methods.
- Attributes are the data associated with an object. They represent the state of the object. In our example,
colorandmakeare attributes. - Methods are functions that belong to an object. They represent the behaviour of the object.
start_engineis a method.
There are two kinds of attributes: instance and class attributes.
Instance attributes are specific to each object. Our color and make are instance attributes because each car can have a different colour and make. They are defined inside the __init__ method.
Class attributes are shared by all objects of a class. If we wanted to say that all cars have four wheels, we could make wheels a class attribute.
class Car:
# Class attribute
wheels = 4
def __init__(self, color, make):
# Instance attributes
self.color = color
self.make = make
my_car = Car("Red", "Tesla")
your_car = Car("Blue", "Rivian")
print(my_car.wheels) # Output: 4
print(your_car.wheels) # Output: 4
print(Car.wheels) # Output: 4
| Attribute Type | Definition | Scope |
|---|---|---|
| Instance | Belongs to a specific object. | Unique to each instance. |
| Class | Shared by all objects of the class. | Same across all instances. |
Encapsulation and Data Hiding
A key principle of OOP is encapsulation. This means bundling an object's data (attributes) and the methods that operate on that data into a single unit, the class. This helps protect the data from accidental modification.
Imagine you're driving a real car. You use the steering wheel to turn; you don't reach in and manually adjust the axle. The car's internal mechanics are hidden from you. You just interact with the public interface (steering wheel, pedals).
In Python, we don't have true "private" attributes like in some other languages, but we use a convention. Prefixing an attribute name with an underscore (_) signals to other developers that it's intended for internal use and shouldn't be modified directly from outside the class.
class BankAccount:
def __init__(self, initial_balance):
# A "private" attribute, indicated by the underscore
self._balance = initial_balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
print(f"Deposited 💲{amount}. New balance is 💲{self._balance}.")
def get_balance(self):
return self._balance
my_account = BankAccount(1000)
# We interact through public methods
my_account.deposit(500)
# We can still access it, but the underscore is a warning not to.
print(my_account._balance) # Output: 1500
Using a double underscore (__) in front of an attribute name invokes name mangling. This makes it harder to access from outside the class, providing a stronger (but still not foolproof) mechanism for data hiding.
By using classes to organize our code into logical, self-contained objects, we create programs that are easier to understand, maintain, and expand.
What is the primary concept behind Object-Oriented Programming (OOP)?
In the analogy of a car blueprint and an actual car, what does the 'blueprint' represent in OOP?