Skip to content

Learning Python: Day 29

Defining attributes and methods for the child class

  • let’s add an attribute specific to electric cars (like a battery) and a method to report on this attribute

class Car:

….–snip–

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 = 40

….def describe_battery(self):

……..”””Print a statement describing the battery size.”””

……..print(f”This car has a {self.battery_size}-kWh battery.”)

my_leaf = ElectricCar(‘nissan’, ‘leag’, 2024)

print(my_leaf.get_descriptive_name())

my_leaf.describe_battery()

  • added a new attribute self.battery_size and set its initial value to 40
  • attribute will be associated with all instances created from the ElectricCar class but won’t be associated with any instances of Car
  • added a method called describe_battery() that prints information about the battery
  • when we call this method, we get a description specific to an electric car

2024 Nissan Leaf

This car has a 40-kWh battery

Overriding methods from the parent class

  • you can override any method form the parent class that won’t work with the child class
  • define a method in the child class with the same name as the method you want to override in the parent class
  • for example, class Car has a method called fill_gas_tank(), this would be meaningless for an all-electric vehicle

class ElectricCar(Car):

….–snip–

….def fill_gas_tank(self):

……..”””Electric cars don’t have gas tanks.”””

……..print(“This car doesn’t have a gas tank!”)

  • calling fill_gas_tank() with an electric car will run this code instead
  • benefit of using inheritance is you can retain info you need and override what you don’t need from the parent class

Instances as attributes

  • when modeling something from the real world in your code, you may find a growing list of attributes and methods, meaning lengthy files
  • composition – breaking a large class into smaller classes that work together
  • if we’re adding many attributes and methods specific to a car’s battery, we can create a separate class called Battery
  • we can use a Battery instance as an attribute in the ElectricCar class

class Car:

….–snip–

class Battery:

….”””A simple attempt to model a battery for an electric car.”””

….def __init-_(self, battery_size=40):

……..”””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.”)

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_leaf = ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

my_leaf.battery.describe_battery()

  • defined a new class called Battery that doesn’t inherit from any other class
  • __init__() method has one parameter, battery_size, in addition to self
  • this is an optional parameter that sets the battery’s size to 40 if no value is provided
  • method describe_battery() has been moved to this class as well
  • in the ElectricCar class, we now add an attribute called self.battery
  • creates a new instance of Battery and assigns that instance to the attribute self.battery
  • this will happen every time the __init__() method is called, any ElectricCar instance will have a Battery instance
  • create an electric car and assign it to the variable my_leaf
  • when we want to describe the battery, we need to work through the car’s battery attribute

my_leaf.battery.describe_battery()

  • this line tells Python to look at the instance my_leaf, find its battery attribute, and call the method describe_battery() associated with Battery instance assigned to the attribute
  • identical output from earlier

2024 Nissan Leaf

This car has a 40-kWh battery

  • looks like extra work, but we can describe the battery in more detail without cluttering the ElectricCar class
  • let’s add another method to Battery that reports the range of the car based on the battery size

class Car:

….–snip–

class Battery:

….–snip–

….def get_range(self):

……..”””Print a statement about the range this battery provides.””””

……..if self.battery_size == 40:

…………range = 150

……..elif self.battery_size == 65:

…………range = 225

……..print(f”This car can go about {range} miles on a full charge.”)

class ElectricCar(Car):

….–snip–

my_leaf = ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

my_leaf.battery.describe_battery()

my_leaf.battery.get_range()

  • method get_range() performs simple analysis
  • if the battery’s capacity is 40 kWh, get_range() sets the range to 150 miles
  • if the capacity is 65 kWh, it sets the range to 225 miles
  • then reports this value
  • call this method through the car’s battery attribute
  • output tells us the range of the car based on its battery size:

2024 Nissan Leaf

This car has a 40-kWh battery.

This car can go about 150 miles on a full charge.

Modeling Real-World Objects

  • you’ll start wrestling with interesting questions when modeling more complicated things like electric cars
  • for example, is the range of an electric car a property of the battery or the car
  • fine to maintain the association of the method get_range() within the Battery class for one car
  • for an entire fleet, we’ll want to move get_range() to the ElectricCar class
  • or we could maintain the association of the get_range() method with the battery but pass it a parameter such as car_model
  • the get_range() method would then report a range based on the battery size and car model
  • in your growth as a programmer, you’ll wrestle with questions like these since you’re thinking at a higher logical level rather than a syntax-focused level
  • not focused on Python, but rather on how to represent the real world in code
  • you’ll realize there are no right or wrong approaches to modeling real-world situations
  • but some approaches are more efficient, it takes practice to find the most efficient representations
  • as long as your code works as you want it, you’re doing well
  • don’t be discouraged when you find you’re ripping apart your classes and rewriting them several times using different approaches

Importing Classes

  • as you add functionality to classes, your files can get long, even if you use inheritance and composition properly
  • keep your files as uncluttered as possible
  • Python lets you store classes in modules and then import the classes you need into your main program

Importing a single class

  • let’s create a module containing just the Car class
  • naming issue: already have a file named car.py in this chapter, this module should be named car.py because it contains code representing a car
  • solution: store the Car class in a module named car.py, replacing the car.py file previously used
  • this point forward, any program using this module will need a more specific filename, such as my_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.

……..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!”)

……..def increment_odometer(self, miles):

…………”””Add the given amount to the odometer reading.”””

…………self.odometer_reading += miles

  • include a module-level docstring that describes the contents of this module
  • write a docstring for each module you create
  • now we make a separate file called my_car.py
  • this file will import the Car class and then create an instance from that class

my_car.py

from car import Car

my_new_car = Car(‘audi’, ‘a4’, 2024)

print(my_new_car.get_descriptive_name())

my_new_car.odometer_reading = 23

my_new_car.read_odometer()

  • import statement tells Python to open the car module and import the class Car

2024 Audi A4

This car has 23 miles on it.

  • importing classes is an effective way to program
  • picture how long this program file would be if the entire Car class were included
  • when you move the class to a module and import the module, same functionality, but cleaner code

Storing multiple classes in a module

  • can store as many classes as needed in a single module, but they should be related
  • classes Battery and ElectricCar both help represent cars, so let’s add them to the module car.py

car.py

“””A set of classes used to represent gas and electric cars.”””

class Car:

….–snip–

class Battery:

….”””A simple attempt to model a battery for an electric car.”””

….def __init__(self, battery_size=40):

……..”””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 == 40:

…………range = 150

……..elif self.battery_size == 65:

…………range = 225

……..print(f”This car can go about {range} miles on a full charge.”)

class ElectricCar(Car):

….”””Models aspect 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()

  • now we can make a new file called my_electric_car.py, import the ElectricCar class, and make an electric car

my_electric_car.py

from car import ElectricCar

my_leaf = ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

my_leaf.battery.describe_battery()

my_leaf.battery.get_range()

  • same output, even though more logic is hidden in a module

2204 Nissan Leaf

This car has a 40-kWh battery.

This car can go about 150 miles on a full charge.

Importing multiple classes from a module

  • can import as many classes as needed into a program file

my_cars.py

from car import Car, Electric Car

my_mustang = Car(‘ford’, ‘mustang’, 2024)

print(my_mustang.get_descriptive_name())

my_leaf = ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

  • import multiple classes form a module by separating each class with a comma
  • after importing classes, you can make as many instances of each class as you need

2024 Ford Mustang

2024 Nissan Leaf

Importing an entire module

  • can import an entire module and then access the classes you need using dot notation
  • since every call that creates an instance of a class includes the module name, you won’t have naming conflicts with any names in the current file
  • let’s import the entire car module and then create a regular car and an electric car

my_cars.py

import car

my_mustang = car.Car(‘ford’, ‘mustang’, 2024)

print(my_mustang.get_descriptive_name())

my_leaf = car.ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

  • first, we import the entire car module
  • then access the classes through the module_name.ClassName syntax
  • creates a Ford Mustang and Nissan Leaf

Importing all classes from a module

  • can import every class from a module

from module_name import *

  • method is not recommended for two reasons
  • first, it’s helpful to read the import statements at the top of a file to understand which classes a program is using
  • this approach can cause confusion with names in the file, if you import a class with the same name as something else in your program file, errors can be hard to diagnose
  • if you need to import many classes from a module, you’re better off importing the entire module and using the module_name.ClassName syntax
  • you’ll see clearly where the module is used in the program and avoid naming conflicts

Importing a module into a module

  • can spread your classes across several modules and avoid storing unrelated classes together
  • a class in one module may depend on a class in another
  • can import required classes into the first module
  • let’s store the Car class in one module and the ElectricCar and Battery classes in a separate module

electric_car.py

“””A set of classes that can be used to represent electric cars.”””

from care import Car

class Battery:

…–snip–

class ElectricCar(Car):

….–snip–

  • ElectricCar needs access its parent class Car, so we import Car directly into the module
  • need to update the Car module so it only contains the Car class

car.py

“””A class that can be used to represent a car.”””

class Car:

….–snip–

  • now we can import from each module separately and create whatever kind of car we need

my_cars.py

from car import Car

from electric_car import ElectricCar

my_mustang = Car(‘ford’, ‘mustang’, 2024)

print(my_mustang.get_descriptive_name())

my_leaf = ElectricCar(‘nissan’, ‘leaf’, 2024)

print(my_leaf.get_descriptive_name())

  • import Car from its module, and ElectricCar from its module
  • then create one regular car and one electric car

2024 Ford Mustang

2024 Nissan Leaf

Using aliases

  • aliases helpful when using modules to organize your projects’ code
  • can use aliases when importing classes
  • for example, a program to make a lot electric cars
  • might get tedious to type (and read) ElectricCar repeatedly
  • can give ElectricCar an alias in the import statement

from electric_car import ElectricCar as EC

  • can use alias now

my_leaf = EC(‘nissan’, ‘leaf’, 2024)

  • can give a module an alias

import electric_car as ec

  • can use module alias with the full class name

my_leaf – ec.ElectricCar(‘nissan’, ‘leaf’, 2024)

Find your own workflow

  • many options to structure code in a large project
  • keep the structure simple in the beginning by doing everything in one file and moving your classes to separate models once everything is working
  • try storing classes in modules when you start a project if you like how models and files interact

The Python standard library

  • Python standard library – set of modules included with every Python installation
  • now that you have a basic understanding of how functions and classes work, you can start to use models like these that other programmers have written
  • can use any function or class in the library by including a simple import statement at the top of your file
  • let’s look at one module, random, which can be useful for real-world situations
  • randint() function takes two integer arguments and returns a randomly selected integer between and including those numbers

>>> from random import randint

>>> randint(1, 6)

3

  • choice() function takes in a list or tuple and returns a randomly chosen element

>>> from random import choice

>>> players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’]

>>> first_up choice(players)

>>> first_up

‘florence’

  • random module shouldn’t be used for building security-related applications, but for fun and interesting projects
  • explore the Python standard library on the site called Python module of the week (https://pymotw.com)

Styling classes

  • CamelCase – capitalize the first letter of each word in the name, avoid underscores, used for classes
  • instance and module names should be lowercase, with underscores between words
  • docstring should follow every class definition to describe what the class does
  • each module should have a docstring describing what the classes can be used for
  • use blank lines to organize code, for example, one blank line between methods in a class or two blank lines to separate classes within a module
  • when importing a module from the standard library and a module that you wrote, place the import statement for the standard library module first
  • then add a blank line and the import statement for the module you wrote

Summary

  • learned how to write classes, store info in a class using attributes, and how to write methods that give your classes the behavior they need
  • learned to write __init__() methods to create instances from your classes with the exact attributes you want
  • saw how to modify attributes of an instance directly and through methods
  • learned that inheritance can simplify the creation of related classes and learned to use instances of one class as attributes for another class for simplicity
  • saw how storing classes in modules and importing classes you need into files where they’ll be used can keep your projects organized
  • started learning about the Python standard library
  • saw an example, based on the random module
  • learned to style your classes

End of study session.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *