# Chapter 9. Classes Objectives: * Making an object from a class is called **instantiation**, and you work with **instances of a class**. * In this chapter you’ll write classes and create instances of those classes. * You’ll specify the kind of information that can be stored in instances, and you’ll define actions that can be taken with these instances. * You’ll also write classes that extend the functionality of existing classes, so similar classes can share code efficiently. * You’ll store your classes in modules and import classes written by other programmers into your own program files. ## Creating and using a class ```python 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!") ``` ### __init__() method * A special method python run automatically whenever we create a new instance. * `self` in parameters is required in method definition, always come first * So method call will automatically pass the `self` argument, it's a reference to itself. ### Making an Instance from a Class ```python my_dog = Dog('Willie', 6) (f"My dog's name is {my_dog.name}.") print(f"My dog is {my_dog.age} years old.") ``` ### Accessing Attributes ```python my_dog.name ``` ### Calling Methods ```python class Dog: --snip-- my_dog = Dog('Willie', 6) my_dog.sit() my_dog.roll_over() ``` ### Creating Multiple Instances ```python class Dog: --snip-- 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 After we create class, we can edit object * need write **getter/setter functions** to access attributes * may need default value for attribute ```python 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.""" 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() ``` * `get_descriptive_name()` is a getter function, so we can get private attributes * `odometer_reading` has default value as 0 ## Inheritance Use **inheritance** when the class is a specialized version of another class. * When one class *inherits* from another, it takes on the attributes and methods of the first class. * Original class is called **parent class**, New class is called **child class** * Child class can define new attributes/methods ### __init__() for a Child Class Creating a `__init__()` of a child class: 1. Call `__init__()` from parent class use `super().__init__()`, to initialize parent's attributes (as shown below) 2. Add specialized attributes ```python 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) ``` * `super()` is a special function that allows us to call a method from parentclass. It will initialize all inherited attributes for ElectricCar ### Defining Attributes and Methods for the Child Class Add attributes/methods for child class ```python 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.") ``` * `self.battery_size` is attribute unique to ElectricCar, not for any parent class * You can add attribute/method for child class as many as you want. ### Overriding Methods from Parent Class Python can disregard parent class's method and only use redefined method in child class ```python class Car: """A simple attempt to represent a car.""" def __init__(self, make, model, year): """Initialize attributes to describe a car.""" ... self.gas_tank = 0 ... def fill_gas_tank(self, gas): """Add gas into tank""" self.gas_tank = self.gas_tank + gas class ElectricCar(Car): """Represent aspects of a car, specific to electric vehicles.""" ... def fill_gas_tank(self): """Electric cars don't have gas tanks""" print("This car doesn't need gas tank!") ``` * `fill_gas_tank()` override parent method, with argument list changed ### Instance as Attributes Sometimes we can use an instance of an class (i.e. an object) as an attribute of another class. e.g. After grouping multiple all battery related attributes/method into a new class ```python 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() ... 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 {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.") ``` Calling these functions are also simple ```python print(my_tesla.get_descriptive_name()) print(my_tesla.battery.describe_battery()) print(my_tesla.battery.get_range()) ``` ### Modeling Real-World Objects Question: `get_range()` is this property of battery or car? Need think on higher-level. ANS: No right or wrong approach. Sometimes it's more efficient, sometimes not. ## Importing Classes Python lets you store classes in modules and then import the classes you need into your main program. ### Importing a Single Class e.g. 1. after separating `Car` class into a python script `car.py` (i.e. **modules**) 2. New python script can reuse class ```python from car import Car my_new_car = Car('audi', 'a4', 2019) print(my_new_car.get_descriptive_name()) ``` ### Storing Multiple Classes in a Module A module (.py script) can stores as many classes as it can. Although these classes should be related. e.g. 1. add `ElectricCar(Car)` & `Battery` into `car.py` module script 2. New python script can reuse these classes ```python from car import ElectricCar my_tesla = ElectricCar('tesla', 'model s', 2019) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range() ``` ### Importing Multiple Classes from a Module ```python from car import Car, ElectricCar my_beetle = Car('volkswagen', 'beetle', 2019) print(my_beetle.get_descriptive_name()) ``` ### Importing an Entire Module ```python import car my_beetle = car.Car('volkswagen', 'beetle', 2019) ``` ### Importing All Classes from a Module ```python from module_name import * ``` ### Importing a Module into a Module ```python from car import Car ... ``` ### Using Aliases Using Aliases can make coding easier (less wordy) ```python from electric_car import ElectricCar as EC ``` ### Finding Your Own Workflow * Keep project simple/flat * When start out, keep code structure simple. * Try doing everything in one file and moving classes to separate modules once everything is working. ## Python Standard Library Python has modules in python std library for use. Use them just like normal customized library ```python from random import randint randint(1,6) ``` ## Styling Classes Following PEP-8: * Class names should be written in *CamelCase* * Every class should have a docstring immediately following the class definition. * Use blank lines to organize code, but don't over-use them. * Sequence of importing modules: Python Standard Library + blanklines + customized modules