Classes
Object-oriented programming is one of the most effective approaches to writing software. In object-oriented programming, you write classes that represent real-world things and situations, and you create objects based on these classes.
Creating and Using a Class
Creating the Dog Class
class Dog:
"""A simple attempt to model a dog."""
def __init__(self, name, age):
"""Initialize name and age attributes."""
self.name = name
self.age = age
def sit(self):
"""Simulate a dog sitting in response to a command."""
print(f"{self.name} is now sitting.")
def roll_over(self):
"""Simulate rolling over in response to a command."""
print(f"{self.name} rolled over!")
By convention, capitalized names refer to classes in Python. There are no parentheses in the class definition because we're creating this class from scratch.
The __init__()
Method
The __init__()
method is a special method that Python runs automatically whenever we create a new instance from the class. The self
parameter is required in the method definition, and it must come first before the other parameters.
Every method call associated with an instance automatically passes self
, which is a reference to the instance itself.
Making an Instance from a Class
class Dog:
"""A simple attempt to model a dog."""
def __init__(self, name, age):
"""Initialize name and age attributes."""
self.name = name
self.age = age
def sit(self):
"""Simulate a dog sitting in response to a command."""
print(f"{self.name} is now sitting.")
def roll_over(self):
"""Simulate rolling over in response to a command."""
print(f"{self.name} rolled over!")
my_dog = Dog('Willie', 6)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
Output:
My dog's name is Willie.
My dog is 6 years old.
Calling Methods
my_dog = Dog('Willie', 6)
my_dog.sit()
my_dog.roll_over()
Output:
Willie is now sitting.
Willie rolled over!
Creating Multiple Instances
my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
my_dog.sit()
print(f"\nYour dog's name is {your_dog.name}.")
print(f"Your dog is {your_dog.age} years old.")
your_dog.sit()
Working with Classes and Instances
The Car Class
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
"""Initialize attributes to describe a car."""
self.make = make
self.model = model
self.year = year
def get_descriptive_name(self):
"""Return a neatly formatted descriptive name."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
Output:
2019 Audi A4
Setting a Default Value for an Attribute
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
"""Initialize attributes to describe a car."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Return a neatly formatted descriptive name."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""Print a statement showing the car's mileage."""
print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
Output:
2019 Audi A4
This car has 0 miles on it.
Modifying Attribute Values
Modifying an Attribute's Value Directly
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
Output:
2019 Audi A4
This car has 23 miles on it.
Modifying an Attribute's Value Through a Method
class Car:
# ... (previous methods)
def update_odometer(self, mileage):
"""Set the odometer reading to the given value."""
self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
You can extend this method to do additional work every time the odometer reading is modified:
def update_odometer(self, mileage):
"""
Set the odometer reading to the given value.
Reject the change if it attempts to roll the odometer back.
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
Incrementing an Attribute's Value Through a Method
class Car:
# ... (previous methods)
def increment_odometer(self, miles):
"""Add the given amount to the odometer reading."""
self.odometer_reading += miles
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23_500)
my_new_car.read_odometer()
my_new_car.increment_odometer(100)
my_new_car.read_odometer()
Output:
2019 Audi A4
This car has 23500 miles on it.
This car has 23600 miles on it.
Inheritance
You don't always have to start from scratch when writing a class. If the class you're writing is a specialized version of another class you wrote, you can use inheritance.
The __init__()
Method for a Child Class
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""Initialize attributes of the parent class."""
super().__init__(make, model, year)
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
Output:
2019 Tesla Model S
The super()
function is a special function that allows you to call a method from the parent class.
Defining Attributes and Methods for the Child Class
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
"""
super().__init__(make, model, year)
self.battery_size = 75
def describe_battery(self):
"""Print a statement describing the battery size."""
print(f"This car has a {self.battery_size}-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
Output:
2019 Tesla Model S
This car has a 75-kWh battery.
Overriding Methods from the Parent Class
class ElectricCar(Car):
# ... (previous methods)
def fill_gas_tank(self):
"""Electric cars don't have gas tanks."""
print("This car doesn't need a gas tank!")
When someone tries to call fill_gas_tank()
with an electric car, Python will ignore the method in Car
and run this method instead.
Instances as Attributes
When modeling something from the real world in code, you may find that you're adding more and more detail to a class. You might find that you have a growing list of attributes and methods and that your files are becoming lengthy. In situations like this, you might recognize that part of one class can be written as a separate class.
class Battery:
"""A simple attempt to model a battery for an electric car."""
def __init__(self, battery_size=75):
"""Initialize the battery's attributes."""
self.battery_size = battery_size
def describe_battery(self):
"""Print a statement describing the battery size."""
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
"""Print a statement about the range this battery provides."""
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge.")
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
"""
super().__init__(make, model, year)
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
Output:
2019 Tesla Model S
This car has a 75-kWh battery.
This car can go about 260 miles on a full charge.
Importing Classes
Importing a Single Class
Create a file called car.py
:
# car.py
"""A class that can be used to represent a car."""
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
"""Initialize attributes to describe a car."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Return a neatly formatted descriptive name."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""Print a statement showing the car's mileage."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Set the odometer reading to the given value."""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""Add the given amount to the odometer reading."""
self.odometer_reading += miles
Now you can import the Car
class and use it:
# my_car.py
from car import Car
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
Storing Multiple Classes in a Module
# car.py
"""A set of classes used to represent gas and electric cars."""
class Car:
# ... (Car class definition)
class Battery:
# ... (Battery class definition)
class ElectricCar(Car):
# ... (ElectricCar class definition)
Importing Multiple Classes from a Module
from car import Car, ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
Importing an Entire Module
import car
my_beetle = car.Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
Importing All Classes from a Module
from car import *
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
This approach is not recommended. It's unclear which classes you're using from the module, and this approach can lead to confusion with names in the file.
The Python Standard Library
The Python Standard Library is a set of modules included with every Python installation. You can use any function or class in the standard library by including a simple import
statement at the top of your file.
from random import randint, choice
# Generate random numbers
print(randint(1, 6))
# Choose random items from a list
players = ['charles', 'martina', 'michael', 'florence', 'eli']
first_up = choice(players)
print(first_up)
Styling Classes
A few styling issues related to classes are worth clarifying:
- Class names should be written in CamelCase
- Instance and module names should be written in lowercase with underscores between words
- Every class should have a docstring immediately following the class definition
- Each module should also have a docstring describing what the classes in a module can be used for
- You can use blank lines to organize code, but don't use them excessively
Summary
In this chapter you learned how to:
- Write your own classes
- Store information in a class using attributes and represent behaviors with methods
- Use
__init__()
methods to create instances from your classes with exactly the attributes you want - Modify the attributes of an instance directly and through methods
- Use inheritance to create classes that extend the functionality of existing classes
- Use instances of one class as attributes in another class to keep each class simple
- Store your classes in modules and import classes written by other programmers
Learning to write classes will help you model almost anything you want to build in your programs. As you work through the remaining chapters, you'll use classes to power your games and visualizations.